diff options
1383 files changed, 28577 insertions, 12148 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index b42f7bc0ca94..e8571757c6f7 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,6 +1,7 @@ [Builtin Hooks] clang_format = true bpfmt = true +ktfmt = true [Builtin Hooks Options] # Only turn on clang-format check for the following subfolders. @@ -17,6 +18,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp tests/ tools/ bpfmt = -d +ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} @@ -25,9 +27,10 @@ hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/c hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} -ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES} - ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES} # This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py. flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH} + +[Tool Paths] +ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar 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/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 11fa7b75182f..c2aeadaea65c 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -619,6 +619,7 @@ public class DeviceIdleController extends SystemService * List of end times for app-IDs that are temporarily marked as being allowed to access * the network and acquire wakelocks. Times are in milliseconds. */ + @GuardedBy("this") private final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes = new SparseArray<>(); @@ -5010,7 +5011,9 @@ public class DeviceIdleController extends SystemService if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { return -1; } - dumpTempWhitelistSchedule(pw, false); + synchronized (this) { + dumpTempWhitelistScheduleLocked(pw, false); + } } } else if ("except-idle-whitelist".equals(cmd)) { getContext().enforceCallingOrSelfPermission( @@ -5294,7 +5297,7 @@ public class DeviceIdleController extends SystemService pw.println(); } } - dumpTempWhitelistSchedule(pw, true); + dumpTempWhitelistScheduleLocked(pw, true); size = mTempWhitelistAppIdArray != null ? mTempWhitelistAppIdArray.length : 0; if (size > 0) { @@ -5422,7 +5425,8 @@ public class DeviceIdleController extends SystemService } } - void dumpTempWhitelistSchedule(PrintWriter pw, boolean printTitle) { + @GuardedBy("this") + void dumpTempWhitelistScheduleLocked(PrintWriter pw, boolean printTitle) { final int size = mTempWhitelistAppIdEndTimes.size(); if (size > 0) { String prefix = ""; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java index adee322f60cf..f722e41c6195 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java @@ -48,6 +48,7 @@ public final class IdleController extends RestrictingController implements Idlen private static final String TAG = "JobScheduler.IdleController"; // Policy: we decide that we're "idle" if the device has been unused / // screen off or dreaming or wireless charging dock idle for at least this long + @GuardedBy("mLock") final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>(); IdlenessTracker mIdleTracker; private final FlexibilityController mFlexibilityController; @@ -118,8 +119,10 @@ public final class IdleController extends RestrictingController implements Idlen for (int i = mTrackedTasks.size()-1; i >= 0; i--) { mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(nowElapsed, isIdle); } + if (!mTrackedTasks.isEmpty()) { + mStateChangedListener.onControllerStateChanged(mTrackedTasks); + } } - mStateChangedListener.onControllerStateChanged(mTrackedTasks); } /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 1e824a19193c..fa8fe3bf5458 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1401,17 +1401,9 @@ public class ActivityManager { public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16; /** - * Restriction reason unknown - do not use directly. - * - * For use with noteAppRestrictionEnabled() - * @hide - */ - public static final int RESTRICTION_REASON_UNKNOWN = 0; - - /** * Restriction reason to be used when this is normal behavior for the state. * - * For use with noteAppRestrictionEnabled() + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) * @hide */ public static final int RESTRICTION_REASON_DEFAULT = 1; @@ -1420,7 +1412,7 @@ public class ActivityManager { * Restriction reason is some kind of timeout that moves the app to a more restricted state. * The threshold should specify how long the app was dormant, in milliseconds. * - * For use with noteAppRestrictionEnabled() + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) * @hide */ public static final int RESTRICTION_REASON_DORMANT = 2; @@ -1429,7 +1421,7 @@ public class ActivityManager { * Restriction reason to be used when removing a restriction due to direct or indirect usage * of the app, especially to undo any automatic restrictions. * - * For use with noteAppRestrictionEnabled() + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) * @hide */ public static final int RESTRICTION_REASON_USAGE = 3; @@ -1438,63 +1430,102 @@ public class ActivityManager { * Restriction reason to be used when the user chooses to manually restrict the app, through * UI or command line interface. * - * For use with noteAppRestrictionEnabled() + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) * @hide */ public static final int RESTRICTION_REASON_USER = 4; /** - * Restriction reason to be used when the user chooses to manually restrict the app on being - * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing - * high battery drain or affecting system performance and the OS recommends that the user - * restrict the app. - * - * For use with noteAppRestrictionEnabled() - * @hide - */ - public static final int RESTRICTION_REASON_USER_NUDGED = 5; - - /** * Restriction reason to be used when the OS automatically detects that the app is causing * system health issues such as performance degradation, battery drain, high memory usage, etc. * - * For use with noteAppRestrictionEnabled() + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) * @hide */ - public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6; + public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 5; /** - * Restriction reason to be used when there is a server-side decision made to restrict an app - * that is showing widespread problems on user devices, or violating policy in some way. + * Restriction reason to be used when app is doing something that is against policy, such as + * spamming the user or being deceptive about its intentions. * - * For use with noteAppRestrictionEnabled() + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) * @hide */ - public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7; + public static final int RESTRICTION_REASON_POLICY = 6; /** * Restriction reason to be used when some other problem requires restricting the app. * - * For use with noteAppRestrictionEnabled() + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) * @hide */ - public static final int RESTRICTION_REASON_OTHER = 8; + public static final int RESTRICTION_REASON_OTHER = 7; /** @hide */ @IntDef(prefix = { "RESTRICTION_REASON_" }, value = { - RESTRICTION_REASON_UNKNOWN, RESTRICTION_REASON_DEFAULT, RESTRICTION_REASON_DORMANT, RESTRICTION_REASON_USAGE, RESTRICTION_REASON_USER, - RESTRICTION_REASON_USER_NUDGED, RESTRICTION_REASON_SYSTEM_HEALTH, - RESTRICTION_REASON_REMOTE_TRIGGER, + RESTRICTION_REASON_POLICY, RESTRICTION_REASON_OTHER }) @Retention(RetentionPolicy.SOURCE) public @interface RestrictionReason{} + /** + * The source of restriction is the user manually choosing to do so. + * + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) + * @hide + */ + public static final int RESTRICTION_SOURCE_USER = 1; + + /** + * The source of restriction is the user, on being prompted by the system for the specified + * reason. + * + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) + * @hide + */ + public static final int RESTRICTION_SOURCE_USER_NUDGED = 2; + + /** + * The source of restriction is the system. + * + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) + * @hide + */ + public static final int RESTRICTION_SOURCE_SYSTEM = 3; + + /** + * The source of restriction is the command line interface through the shell or a test. + * + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) + * @hide + */ + public static final int RESTRICTION_SOURCE_COMMAND_LINE = 4; + + /** + * The source of restriction is a configuration pushed from a server. + * + * @see #noteAppRestrictionEnabled(String, int, int, boolean, int, String, int, long) + * @hide + */ + public static final int RESTRICTION_SOURCE_REMOTE_TRIGGER = 5; + + /** @hide */ + @IntDef(prefix = { "RESTRICTION_SOURCE_" }, value = { + RESTRICTION_SOURCE_USER, + RESTRICTION_SOURCE_USER_NUDGED, + RESTRICTION_SOURCE_SYSTEM, + RESTRICTION_SOURCE_COMMAND_LINE, + RESTRICTION_SOURCE_REMOTE_TRIGGER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RestrictionSource{} + /** @hide */ @android.ravenwood.annotation.RavenwoodKeep public static String restrictionLevelToName(@RestrictionLevel int level) { @@ -1724,6 +1755,12 @@ public class ActivityManager { private int mNavigationBarColor; @Appearance private int mSystemBarsAppearance; + /** + * Similar to {@link TaskDescription#mSystemBarsAppearance}, but is taken from the topmost + * fully opaque (i.e. non transparent) activity in the task. + */ + @Appearance + private int mTopOpaqueSystemBarsAppearance; private boolean mEnsureStatusBarContrastWhenTransparent; private boolean mEnsureNavigationBarContrastWhenTransparent; private int mResizeMode; @@ -1824,7 +1861,7 @@ public class ActivityManager { final Icon icon = mIconRes == Resources.ID_NULL ? null : Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes); return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor, - mStatusBarColor, mNavigationBarColor, 0, false, false, + mStatusBarColor, mNavigationBarColor, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } } @@ -1843,7 +1880,7 @@ public class ActivityManager { @Deprecated public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) { this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes), - colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + colorPrimary, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) { throw new RuntimeException("A TaskDescription's primary color should be opaque"); } @@ -1861,7 +1898,7 @@ public class ActivityManager { @Deprecated public TaskDescription(String label, @DrawableRes int iconRes) { this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes), - 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } /** @@ -1873,7 +1910,7 @@ public class ActivityManager { */ @Deprecated public TaskDescription(String label) { - this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + this(label, null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } /** @@ -1883,7 +1920,7 @@ public class ActivityManager { */ @Deprecated public TaskDescription() { - this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + this(null, null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } /** @@ -1899,7 +1936,7 @@ public class ActivityManager { @Deprecated public TaskDescription(String label, Bitmap icon, int colorPrimary) { this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0, - 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); + 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) { throw new RuntimeException("A TaskDescription's primary color should be opaque"); } @@ -1915,7 +1952,7 @@ public class ActivityManager { */ @Deprecated public TaskDescription(String label, Bitmap icon) { - this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false, + this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0); } @@ -1924,6 +1961,7 @@ public class ActivityManager { int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor, @Appearance int systemBarsAppearance, + @Appearance int topOpaqueSystemBarsAppearance, boolean ensureStatusBarContrastWhenTransparent, boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth, int minHeight, int colorBackgroundFloating) { @@ -1934,6 +1972,7 @@ public class ActivityManager { mStatusBarColor = statusBarColor; mNavigationBarColor = navigationBarColor; mSystemBarsAppearance = systemBarsAppearance; + mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance; mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = ensureNavigationBarContrastWhenTransparent; @@ -1963,6 +2002,7 @@ public class ActivityManager { mStatusBarColor = other.mStatusBarColor; mNavigationBarColor = other.mNavigationBarColor; mSystemBarsAppearance = other.mSystemBarsAppearance; + mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance; mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = other.mEnsureNavigationBarContrastWhenTransparent; @@ -1995,6 +2035,9 @@ public class ActivityManager { if (other.mSystemBarsAppearance != 0) { mSystemBarsAppearance = other.mSystemBarsAppearance; } + if (other.mTopOpaqueSystemBarsAppearance != 0) { + mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance; + } mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = @@ -2274,6 +2317,14 @@ public class ActivityManager { /** * @hide */ + @Appearance + public int getTopOpaqueSystemBarsAppearance() { + return mTopOpaqueSystemBarsAppearance; + } + + /** + * @hide + */ public void setEnsureStatusBarContrastWhenTransparent( boolean ensureStatusBarContrastWhenTransparent) { mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent; @@ -2289,6 +2340,13 @@ public class ActivityManager { /** * @hide */ + public void setTopOpaqueSystemBarsAppearance(int topOpaqueSystemBarsAppearance) { + mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance; + } + + /** + * @hide + */ public boolean getEnsureNavigationBarContrastWhenTransparent() { return mEnsureNavigationBarContrastWhenTransparent; } @@ -2411,6 +2469,7 @@ public class ActivityManager { dest.writeInt(mStatusBarColor); dest.writeInt(mNavigationBarColor); dest.writeInt(mSystemBarsAppearance); + dest.writeInt(mTopOpaqueSystemBarsAppearance); dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent); dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent); dest.writeInt(mResizeMode); @@ -2435,6 +2494,7 @@ public class ActivityManager { mStatusBarColor = source.readInt(); mNavigationBarColor = source.readInt(); mSystemBarsAppearance = source.readInt(); + mTopOpaqueSystemBarsAppearance = source.readInt(); mEnsureStatusBarContrastWhenTransparent = source.readBoolean(); mEnsureNavigationBarContrastWhenTransparent = source.readBoolean(); mResizeMode = source.readInt(); @@ -2467,7 +2527,8 @@ public class ActivityManager { + " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode) + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight + " colorBackgrounFloating: " + mColorBackgroundFloating - + " systemBarsAppearance: " + mSystemBarsAppearance; + + " systemBarsAppearance: " + mSystemBarsAppearance + + " topOpaqueSystemBarsAppearance: " + mTopOpaqueSystemBarsAppearance; } @Override @@ -2488,6 +2549,7 @@ public class ActivityManager { result = result * 31 + mStatusBarColor; result = result * 31 + mNavigationBarColor; result = result * 31 + mSystemBarsAppearance; + result = result * 31 + mTopOpaqueSystemBarsAppearance; result = result * 31 + (mEnsureStatusBarContrastWhenTransparent ? 1 : 0); result = result * 31 + (mEnsureNavigationBarContrastWhenTransparent ? 1 : 0); result = result * 31 + mResizeMode; @@ -2511,6 +2573,7 @@ public class ActivityManager { && mStatusBarColor == other.mStatusBarColor && mNavigationBarColor == other.mNavigationBarColor && mSystemBarsAppearance == other.mSystemBarsAppearance + && mTopOpaqueSystemBarsAppearance == other.mTopOpaqueSystemBarsAppearance && mEnsureStatusBarContrastWhenTransparent == other.mEnsureStatusBarContrastWhenTransparent && mEnsureNavigationBarContrastWhenTransparent @@ -6254,7 +6317,7 @@ public class ActivityManager { * <p> * The {@code enabled} value determines whether the state is being applied or removed. * Not all restrictions are actual restrictions. For example, - * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle + * {@link #RESTRICTION_LEVEL_ADAPTIVE_BUCKET} is a normal state, where there is default lifecycle * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the * app is being put in a power-save allowlist. * <p> @@ -6267,6 +6330,7 @@ public class ActivityManager { * true, * RESTRICTION_REASON_USER, * "settings", + * RESTRICTION_SOURCE_USER, * 0); * </pre> * Example arguments when app is put in restricted standby bucket for exceeding X hours of jobs: @@ -6278,6 +6342,7 @@ public class ActivityManager { * true, * RESTRICTION_REASON_SYSTEM_HEALTH, * "job_duration", + * RESTRICTION_SOURCE_SYSTEM, * X * 3600 * 1000L); * </pre> * @@ -6295,7 +6360,7 @@ public class ActivityManager { * Examples of system resource usage: wakelock, wakeups, mobile_data, * binder_calls, memory, excessive_threads, excessive_cpu, gps_scans, etc. * Examples of user actions: settings, notification, command_line, launch, etc. - * + * @param source the source of the action, from {@code RestrictionSource} * @param threshold for reasons that are due to exceeding some threshold, the threshold value * must be specified. The unit of the threshold depends on the reason and/or * subReason. For time, use milliseconds. For memory, use KB. For count, use @@ -6308,10 +6373,10 @@ public class ActivityManager { public void noteAppRestrictionEnabled(@NonNull String packageName, int uid, @RestrictionLevel int restrictionLevel, boolean enabled, @RestrictionReason int reason, - @Nullable String subReason, long threshold) { + @Nullable String subReason, @RestrictionSource int source, long threshold) { try { getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled, - reason, subReason, threshold); + reason, subReason, source, threshold); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index c0f723241c82..5956e2bde242 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2617,6 +2617,9 @@ public class ApplicationPackageManager extends PackageManager { try { Objects.requireNonNull(packageName); return mPM.isAppArchivable(packageName, new UserHandle(getUserId())); + } catch (ParcelableException e) { + e.maybeRethrow(NameNotFoundException.class); + throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index e8b57f224a0b..15b13dc97554 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -1016,5 +1016,5 @@ interface IActivityManager { */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)") void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType, - boolean enabled, int reason, in String subReason, long threshold); + boolean enabled, int reason, in String subReason, int source, long threshold); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index dfae48b82909..329fb00c1d9b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1632,6 +1632,10 @@ public class Notification implements Parcelable private Icon mSmallIcon; @UnsupportedAppUsage private Icon mLargeIcon; + private Icon mAppIcon; + + /** Cache for whether the notification was posted by a headless system app. */ + private Boolean mBelongsToHeadlessSystemApp = null; @UnsupportedAppUsage private String mChannelId; @@ -3079,25 +3083,17 @@ public class Notification implements Parcelable return name.toString(); } } - // If not, try getting the app info from extras. + // If not, try getting the name from the app info. if (context == null) { return null; } - final PackageManager pm = context.getPackageManager(); if (TextUtils.isEmpty(name)) { - if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { - final ApplicationInfo info = extras.getParcelable( - EXTRA_BUILDER_APPLICATION_INFO, - ApplicationInfo.class); - if (info != null) { - name = pm.getApplicationLabel(info); - } + ApplicationInfo info = getApplicationInfo(context); + if (info != null) { + final PackageManager pm = context.getPackageManager(); + name = pm.getApplicationLabel(getApplicationInfo(context)); } } - // If that's still empty, use the one from the context directly. - if (TextUtils.isEmpty(name)) { - name = pm.getApplicationLabel(context.getApplicationInfo()); - } // If there's still nothing, ¯\_(ツ)_/¯ if (TextUtils.isEmpty(name)) { return null; @@ -3109,9 +3105,89 @@ public class Notification implements Parcelable } /** + * Whether this notification was posted by a headless system app. + * + * If we don't have enough information to figure this out, this will return false. Therefore, + * false negatives are possible, but false positives should not be. + * + * @hide + */ + public boolean belongsToHeadlessSystemApp(Context context) { + Trace.beginSection("Notification#belongsToHeadlessSystemApp"); + + try { + if (mBelongsToHeadlessSystemApp != null) { + return mBelongsToHeadlessSystemApp; + } + + if (context == null) { + // Without a valid context, we don't know exactly. Let's assume it doesn't belong to + // a system app, but not cache the value. + return false; + } + + ApplicationInfo info = getApplicationInfo(context); + if (info != null) { + if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + // It's not a system app at all. + mBelongsToHeadlessSystemApp = false; + } else { + // If there's no launch intent, it's probably a headless app. + final PackageManager pm = context.getPackageManager(); + mBelongsToHeadlessSystemApp = pm.getLaunchIntentForPackage(info.packageName) + == null; + } + } else { + // If for some reason we don't have the app info, we don't know; best assume it's + // not a system app. + return false; + } + return mBelongsToHeadlessSystemApp; + } finally { + Trace.endSection(); + } + } + + /** + * Get the resource ID of the app icon from application info. * @hide */ - public int loadHeaderAppIconRes(Context context) { + public int getHeaderAppIconRes(Context context) { + ApplicationInfo info = getApplicationInfo(context); + if (info != null) { + return info.icon; + } + return 0; + } + + /** + * Load the app icon drawable from the package manager. This could result in a binder call. + * @hide + */ + public Drawable loadHeaderAppIcon(Context context) { + Trace.beginSection("Notification#loadHeaderAppIcon"); + + try { + if (context == null) { + Log.e(TAG, "Cannot load the app icon drawable with a null context"); + return null; + } + final PackageManager pm = context.getPackageManager(); + ApplicationInfo info = getApplicationInfo(context); + if (info == null) { + Log.e(TAG, "Cannot load the app icon drawable: no application info"); + return null; + } + return pm.getApplicationIcon(info); + } finally { + Trace.endSection(); + } + } + + /** + * Fetch the application info from the notification, or the context if that isn't available. + */ + private ApplicationInfo getApplicationInfo(Context context) { ApplicationInfo info = null; if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { info = extras.getParcelable( @@ -3119,12 +3195,12 @@ public class Notification implements Parcelable ApplicationInfo.class); } if (info == null) { + if (context == null) { + return null; + } info = context.getApplicationInfo(); } - if (info != null) { - return info.icon; - } - return 0; + return info; } /** @@ -4124,6 +4200,55 @@ public class Notification implements Parcelable } /** + * The colored app icon that can replace the small icon in the notification starting in V. + * + * Before using this value, you should first check whether it's actually being used by the + * notification by calling {@link Notification#shouldUseAppIcon()}. + * + * @hide + */ + public Icon getAppIcon() { + if (mAppIcon != null) { + return mAppIcon; + } + // If the app icon hasn't been loaded yet, check if we can load it without a context. + if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { + final ApplicationInfo info = extras.getParcelable( + EXTRA_BUILDER_APPLICATION_INFO, + ApplicationInfo.class); + if (info != null) { + int appIconRes = info.icon; + if (appIconRes == 0) { + Log.w(TAG, "Failed to get the app icon: no icon in application info"); + return null; + } + mAppIcon = Icon.createWithResource(info.packageName, appIconRes); + return mAppIcon; + } else { + Log.e(TAG, "Failed to get the app icon: " + + "there's an EXTRA_BUILDER_APPLICATION_INFO in extras but it's null"); + } + } else { + Log.w(TAG, "Failed to get the app icon: no application info in extras"); + } + return null; + } + + /** + * Whether the notification is using the app icon instead of the small icon. + * @hide + */ + public boolean shouldUseAppIcon() { + if (Flags.notificationsUseAppIconInRow()) { + if (belongsToHeadlessSystemApp(/* context = */ null)) { + return false; + } + return getAppIcon() != null; + } + return false; + } + + /** * The large icon shown in this notification's content view. * @see Builder#getLargeIcon() * @see Builder#setLargeIcon(Icon) @@ -5637,6 +5762,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())); } } @@ -6112,16 +6241,30 @@ public class Notification implements Parcelable if (Flags.notificationsUseAppIcon()) { // Override small icon with app icon mN.mSmallIcon = Icon.createWithResource(mContext, - mN.loadHeaderAppIconRes(mContext)); + mN.getHeaderAppIconRes(mContext)); } else if (mN.mSmallIcon == null && mN.icon != 0) { mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); } - contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); + boolean usingAppIcon = false; + if (Flags.notificationsUseAppIconInRow() && !mN.belongsToHeadlessSystemApp(mContext)) { + // Use the app icon in the view + int appIconRes = mN.getHeaderAppIconRes(mContext); + if (appIconRes != 0) { + mN.mAppIcon = Icon.createWithResource(mContext, appIconRes); + contentView.setImageViewIcon(R.id.icon, mN.mAppIcon); + usingAppIcon = true; + } else { + Log.w(TAG, "bindSmallIcon: could not get the app icon"); + } + } + if (!usingAppIcon) { + contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); + } contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); // Don't change color if we're using the app icon. - if (!Flags.notificationsUseAppIcon()) { + if (!Flags.notificationsUseAppIcon() && !usingAppIcon) { processSmallIconColor(mN.mSmallIcon, contentView, p); } } @@ -8924,10 +9067,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 +9089,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..6edae0b60fd9 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -52,14 +52,32 @@ flag { bug: "281044385" } +# vvv Prototypes for using app icons in notifications vvv + flag { name: "notifications_use_app_icon" namespace: "systemui" - description: "Experiment to replace the small icon in a notification with the app icon." + description: "Experiment to replace the small icon in a notification with the app icon. This includes the status bar, AOD, shelf and notification row itself." bug: "335211019" } flag { + name: "notifications_use_app_icon_in_row" + namespace: "systemui" + description: "Experiment to replace the small icon in a notification row with the app icon." + bug: "335211019" +} + +flag { + name: "notifications_use_monochrome_app_icon" + namespace: "systemui" + description: "Experiment to replace the notification icon in the status bar and shelf with the monochrome app icon, if available." + bug: "335211019" +} + +# ^^^ Prototypes for using app icons in notifications ^^^ + +flag { name: "notification_expansion_optional" namespace: "systemui" description: "Experiment to restore the pre-S behavior where standard notifications are not expandable unless they have actions." @@ -166,4 +184,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/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index 7dcbebaeba0b..7819e1ef94c6 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -104,8 +104,8 @@ public class LaunchActivityItem extends ClientTransactionItem { public void execute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); - ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, mInfo, - mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState, + final ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, + mInfo, mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, mPendingNewIntents, mSceneTransitionInfo, mIsForward, mProfilerInfo, client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken, mInitialCallerInfoAccessToken, mActivityWindowInfo); diff --git a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java index ee04f8cbe3c7..ed18a057118c 100644 --- a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java +++ b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java @@ -16,8 +16,6 @@ package android.app.servertransaction; -import static java.util.Objects.requireNonNull; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ClientTransactionHandler; @@ -35,23 +33,19 @@ import java.util.Objects; * Message to deliver window insets control change info. * @hide */ -public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { +public class WindowStateInsetsControlChangeItem extends WindowStateTransactionItem { private static final String TAG = "WindowStateInsetsControlChangeItem"; - private IWindow mWindow; private InsetsState mInsetsState; private InsetsSourceControl.Array mActiveControls; @Override - public void execute(@NonNull ClientTransactionHandler client, + public void execute(@NonNull ClientTransactionHandler client, @NonNull IWindow window, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "windowInsetsControlChanged"); - if (mWindow instanceof InsetsControlChangeListener listener) { - listener.onExecutingWindowStateInsetsControlChangeItem(); - } try { - mWindow.insetsControlChanged(mInsetsState, mActiveControls); + window.insetsControlChanged(mInsetsState, mActiveControls); } catch (RemoteException e) { // Should be a local call. // An exception could happen if the process is restarted. It is safe to ignore since @@ -73,16 +67,21 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { if (instance == null) { instance = new WindowStateInsetsControlChangeItem(); } - instance.mWindow = requireNonNull(window); + instance.setWindow(window); instance.mInsetsState = new InsetsState(insetsState, true /* copySources */); - instance.mActiveControls = new InsetsSourceControl.Array(activeControls); + instance.mActiveControls = new InsetsSourceControl.Array( + activeControls, true /* copyControls */); + // This source control is an extra copy if the client is not local. By setting + // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of + // SurfaceControl.writeToParcel. + instance.mActiveControls.setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE); return instance; } @Override public void recycle() { - mWindow = null; + super.recycle(); mInsetsState = null; mActiveControls = null; ObjectPool.recycle(this); @@ -93,14 +92,14 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { /** Writes to Parcel. */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeStrongBinder(mWindow.asBinder()); + super.writeToParcel(dest, flags); dest.writeTypedObject(mInsetsState, flags); dest.writeTypedObject(mActiveControls, flags); } /** Reads from Parcel. */ private WindowStateInsetsControlChangeItem(@NonNull Parcel in) { - mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + super(in); mInsetsState = in.readTypedObject(InsetsState.CREATOR); mActiveControls = in.readTypedObject(InsetsSourceControl.Array.CREATOR); @@ -122,19 +121,18 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!super.equals(o)) { return false; } final WindowStateInsetsControlChangeItem other = (WindowStateInsetsControlChangeItem) o; - return Objects.equals(mWindow, other.mWindow) - && Objects.equals(mInsetsState, other.mInsetsState) + return Objects.equals(mInsetsState, other.mInsetsState) && Objects.equals(mActiveControls, other.mActiveControls); } @Override public int hashCode() { int result = 17; - result = 31 * result + Objects.hashCode(mWindow); + result = 31 * result + super.hashCode(); result = 31 * result + Objects.hashCode(mInsetsState); result = 31 * result + Objects.hashCode(mActiveControls); return result; @@ -142,15 +140,6 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { @Override public String toString() { - return "WindowStateInsetsControlChangeItem{window=" + mWindow + "}"; - } - - /** The interface for IWindow to perform insets control change directly if possible. */ - public interface InsetsControlChangeListener { - /** - * Notifies that IWindow#insetsControlChanged is going to be called from - * WindowStateInsetsControlChangeItem. - */ - void onExecutingWindowStateInsetsControlChangeItem(); + return "WindowStateInsetsControlChangeItem{" + super.toString() + "}"; } } diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java index da99096f022a..3c1fa4b83340 100644 --- a/core/java/android/app/servertransaction/WindowStateResizeItem.java +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -18,8 +18,6 @@ package android.app.servertransaction; import static android.view.Display.INVALID_DISPLAY; -import static java.util.Objects.requireNonNull; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ClientTransactionHandler; @@ -39,11 +37,10 @@ import java.util.Objects; * Message to deliver window resize info. * @hide */ -public class WindowStateResizeItem extends ClientTransactionItem { +public class WindowStateResizeItem extends WindowStateTransactionItem { private static final String TAG = "WindowStateResizeItem"; - private IWindow mWindow; private ClientWindowFrames mFrames; private boolean mReportDraw; private MergedConfiguration mConfiguration; @@ -59,15 +56,12 @@ public class WindowStateResizeItem extends ClientTransactionItem { private ActivityWindowInfo mActivityWindowInfo; @Override - public void execute(@NonNull ClientTransactionHandler client, + public void execute(@NonNull ClientTransactionHandler client, @NonNull IWindow window, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, mReportDraw ? "windowResizedReport" : "windowResized"); - if (mWindow instanceof ResizeListener listener) { - listener.onExecutingWindowStateResizeItem(); - } try { - mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, + window.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing, mActivityWindowInfo); } catch (RemoteException e) { @@ -94,7 +88,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { if (instance == null) { instance = new WindowStateResizeItem(); } - instance.mWindow = requireNonNull(window); + instance.setWindow(window); instance.mFrames = new ClientWindowFrames(frames); instance.mReportDraw = reportDraw; instance.mConfiguration = new MergedConfiguration(configuration); @@ -113,7 +107,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { @Override public void recycle() { - mWindow = null; + super.recycle(); mFrames = null; mReportDraw = false; mConfiguration = null; @@ -132,7 +126,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { /** Writes to Parcel. */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeStrongBinder(mWindow.asBinder()); + super.writeToParcel(dest, flags); dest.writeTypedObject(mFrames, flags); dest.writeBoolean(mReportDraw); dest.writeTypedObject(mConfiguration, flags); @@ -147,7 +141,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { /** Reads from Parcel. */ private WindowStateResizeItem(@NonNull Parcel in) { - mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + super(in); mFrames = in.readTypedObject(ClientWindowFrames.CREATOR); mReportDraw = in.readBoolean(); mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR); @@ -175,12 +169,11 @@ public class WindowStateResizeItem extends ClientTransactionItem { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!super.equals(o)) { return false; } final WindowStateResizeItem other = (WindowStateResizeItem) o; - return Objects.equals(mWindow, other.mWindow) - && Objects.equals(mFrames, other.mFrames) + return Objects.equals(mFrames, other.mFrames) && mReportDraw == other.mReportDraw && Objects.equals(mConfiguration, other.mConfiguration) && Objects.equals(mInsetsState, other.mInsetsState) @@ -195,7 +188,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { @Override public int hashCode() { int result = 17; - result = 31 * result + Objects.hashCode(mWindow); + result = 31 * result + super.hashCode(); result = 31 * result + Objects.hashCode(mFrames); result = 31 * result + (mReportDraw ? 1 : 0); result = 31 * result + Objects.hashCode(mConfiguration); @@ -211,16 +204,10 @@ public class WindowStateResizeItem extends ClientTransactionItem { @Override public String toString() { - return "WindowStateResizeItem{window=" + mWindow + return "WindowStateResizeItem{" + super.toString() + ", reportDrawn=" + mReportDraw + ", configuration=" + mConfiguration + ", activityWindowInfo=" + mActivityWindowInfo + "}"; } - - /** The interface for IWindow to perform resize directly if possible. */ - public interface ResizeListener { - /** Notifies that IWindow#resized is going to be called from WindowStateResizeItem. */ - void onExecutingWindowStateResizeItem(); - } } diff --git a/core/java/android/app/servertransaction/WindowStateTransactionItem.java b/core/java/android/app/servertransaction/WindowStateTransactionItem.java new file mode 100644 index 000000000000..d556363dd947 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowStateTransactionItem.java @@ -0,0 +1,118 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import static java.util.Objects.requireNonNull; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.os.Parcel; +import android.view.IWindow; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + +/** + * {@link ClientTransactionItem} to report changes to a window. + * + * @hide + */ +public abstract class WindowStateTransactionItem extends ClientTransactionItem { + + /** The interface for IWindow to perform callback directly if possible. */ + public interface TransactionListener { + /** Notifies that the transaction item is going to be executed. */ + void onExecutingWindowStateTransactionItem(); + } + + /** Target window. */ + private IWindow mWindow; + + WindowStateTransactionItem() {} + + @Override + public final void execute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { + if (mWindow instanceof TransactionListener listener) { + listener.onExecutingWindowStateTransactionItem(); + } + execute(client, mWindow, pendingActions); + } + + /** + * Like {@link #execute(ClientTransactionHandler, PendingTransactionActions)}, + * but take non-null {@link IWindow} as a parameter. + */ + @VisibleForTesting(visibility = PACKAGE) + public abstract void execute(@NonNull ClientTransactionHandler client, + @NonNull IWindow window, @NonNull PendingTransactionActions pendingActions); + + void setWindow(@NonNull IWindow window) { + mWindow = requireNonNull(window); + } + + // To be overridden + + WindowStateTransactionItem(@NonNull Parcel in) { + mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + } + + @CallSuper + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mWindow.asBinder()); + } + + @CallSuper + @Override + public void recycle() { + mWindow = null; + } + + // Subclass must override and call super.equals to compare the mActivityToken. + @SuppressWarnings("EqualsGetClass") + @CallSuper + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowStateTransactionItem other = (WindowStateTransactionItem) o; + return Objects.equals(mWindow, other.mWindow); + } + + @CallSuper + @Override + public int hashCode() { + return Objects.hashCode(mWindow); + } + + @CallSuper + @Override + public String toString() { + return "mWindow=" + mWindow; + } +} 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/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 006226eb8c31..ed5d66227574 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -52,4 +52,15 @@ flag { description: "Makes MediaDrm APIs device-aware" bug: "303535376" is_fixed_read_only: true -}
\ No newline at end of file +} + +flag { + namespace: "virtual_devices" + name: "virtual_display_multi_window_mode_support" + description: "Add support for WINDOWING_MODE_MULTI_WINDOW to virtual displays by default" + is_fixed_read_only: true + bug: "341151395" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 270fc32a4e32..bbd0e9ff4738 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2431,6 +2431,7 @@ public class PackageInstaller { statusReceiver, new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); + throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2467,6 +2468,7 @@ public class PackageInstaller { } catch (ParcelableException e) { e.maybeRethrow(IOException.class); e.maybeRethrow(PackageManager.NameNotFoundException.class); + throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2499,6 +2501,7 @@ public class PackageInstaller { userActionIntent, new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); + throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 2d9881abc4a5..282ede385ba3 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4397,8 +4397,6 @@ public abstract class PackageManager { * {@link #hasSystemFeature}: The device supports freeform window management. * Windows have title bars and can be moved and resized. */ - // If this feature is present, you also need to set - // com.android.internal.R.config_freeformWindowManagement to true in your configuration overlay. @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT = "android.software.freeform_window_management"; diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index a720b6473be5..248ef1d4d9c4 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2745,17 +2745,6 @@ public class Resources { ar.recycle(); Log.i(TAG, "...preloaded " + numberOfEntries + " resources in " + (SystemClock.uptimeMillis() - startTime) + "ms."); - - if (sysRes.getBoolean( - com.android.internal.R.bool.config_freeformWindowManagement)) { - startTime = SystemClock.uptimeMillis(); - ar = sysRes.obtainTypedArray( - com.android.internal.R.array.preloaded_freeform_multi_window_drawables); - numberOfEntries = preloadDrawables(sysRes, ar); - ar.recycle(); - Log.i(TAG, "...preloaded " + numberOfEntries + " resource in " - + (SystemClock.uptimeMillis() - startTime) + "ms."); - } } sysRes.finishPreloading(); } catch (RuntimeException e) { diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 1eb466cb10a3..f54be00c9e69 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 @@ -731,7 +733,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * commits, or is rolled back, either explicitly or by a call to * {@link #yieldIfContendedSafely}. */ - // TODO(340874899) Provide an Executor overload + @SuppressLint("ExecutorRegistration") public void beginTransactionWithListener( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, true); @@ -761,7 +763,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * transaction begins, commits, or is rolled back, either * explicitly or by a call to {@link #yieldIfContendedSafely}. */ - // TODO(340874899) Provide an Executor overload + @SuppressLint("ExecutorRegistration") public void beginTransactionWithListenerNonExclusive( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, false); @@ -787,7 +789,6 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </pre> */ - // TODO(340874899) Provide an Executor overload @SuppressLint("ExecutorRegistration") @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public void beginTransactionWithListenerReadOnly( diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 60ad8e81fcf4..2d3d25217357 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -523,23 +523,25 @@ public class SystemSensorManager extends SensorManager { Handler mainHandler = new Handler(mContext.getMainLooper()); - for (Map.Entry<DynamicSensorCallback, Handler> entry : - mDynamicSensorCallbacks.entrySet()) { - final DynamicSensorCallback callback = entry.getKey(); - Handler handler = - entry.getValue() == null ? mainHandler : entry.getValue(); - - handler.post(new Runnable() { - @Override - public void run() { - for (Sensor s: addedList) { - callback.onDynamicSensorConnected(s); + synchronized (mDynamicSensorCallbacks) { + for (Map.Entry<DynamicSensorCallback, Handler> entry : + mDynamicSensorCallbacks.entrySet()) { + final DynamicSensorCallback callback = entry.getKey(); + Handler handler = + entry.getValue() == null ? mainHandler : entry.getValue(); + + handler.post(new Runnable() { + @Override + public void run() { + for (Sensor s: addedList) { + callback.onDynamicSensorConnected(s); + } + for (Sensor s: removedList) { + callback.onDynamicSensorDisconnected(s); + } } - for (Sensor s: removedList) { - callback.onDynamicSensorDisconnected(s); - } - } - }); + }); + } } for (Sensor s: removedList) { @@ -658,13 +660,15 @@ public class SystemSensorManager extends SensorManager { if (callback == null) { throw new IllegalArgumentException("callback cannot be null"); } - if (mDynamicSensorCallbacks.containsKey(callback)) { - // has been already registered, ignore - return; - } + synchronized (mDynamicSensorCallbacks) { + if (mDynamicSensorCallbacks.containsKey(callback)) { + // has been already registered, ignore + return; + } - setupDynamicSensorBroadcastReceiver(); - mDynamicSensorCallbacks.put(callback, handler); + setupDynamicSensorBroadcastReceiver(); + mDynamicSensorCallbacks.put(callback, handler); + } } /** @hide */ @@ -673,7 +677,9 @@ public class SystemSensorManager extends SensorManager { if (DEBUG_DYNAMIC_SENSOR) { Log.i(TAG, "Removing dynamic sensor listener"); } - mDynamicSensorCallbacks.remove(callback); + synchronized (mDynamicSensorCallbacks) { + mDynamicSensorCallbacks.remove(callback); + } } /* diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig index 4284ad09e251..047d1fa4f49a 100644 --- a/core/java/android/hardware/biometrics/flags.aconfig +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -32,3 +32,10 @@ flag { description: "Feature flag for adding a custom content view API to BiometricPrompt.Builder." bug: "302735104" } + +flag { + name: "mandatory_biometrics" + namespace: "biometrics_framework" + description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations" + bug: "322081563" +} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 342479bc159e..3cc87ea9d359 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1492,10 +1492,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Default flash brightness level for manual flash control in SINGLE mode.</p> * <p>If flash unit is available this will be greater than or equal to 1 and less - * or equal to <code>android.flash.info.singleStrengthMaxLevel</code>. + * or equal to {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}. * Note for devices that do not support the manual flash strength control * feature, this level will always be equal to 1.</p> * <p>This key is available on all devices.</p> + * + * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL */ @PublicKey @NonNull @@ -1511,13 +1513,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * otherwise the value will be equal to 1.</p> * <p>Note that this level is just a number of supported levels(the granularity of control). * There is no actual physical power units tied to this level. - * There is no relation between android.flash.info.torchStrengthMaxLevel and - * android.flash.info.singleStrengthMaxLevel i.e. the ratio of - * android.flash.info.torchStrengthMaxLevel:android.flash.info.singleStrengthMaxLevel + * There is no relation between {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} and + * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} i.e. the ratio of + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}:{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} * is not guaranteed to be the ratio of actual brightness.</p> * <p>This key is available on all devices.</p> * * @see CaptureRequest#FLASH_MODE + * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL + * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL */ @PublicKey @NonNull @@ -1528,10 +1532,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Default flash brightness level for manual flash control in TORCH mode</p> * <p>If flash unit is available this will be greater than or equal to 1 and less - * or equal to android.flash.info.torchStrengthMaxLevel. + * or equal to {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}. * Note for the devices that do not support the manual flash strength control feature, * this level will always be equal to 1.</p> * <p>This key is available on all devices.</p> + * + * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL */ @PublicKey @NonNull diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index c82e7e8c12ef..938636f7e8e5 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -2684,35 +2684,39 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>Flash strength level to be used when manual flash control is active.</p> * <p>Flash strength level to use in capture mode i.e. when the applications control * flash with either SINGLE or TORCH mode.</p> - * <p>Use android.flash.info.singleStrengthMaxLevel and - * android.flash.info.torchStrengthMaxLevel to check whether the device supports + * <p>Use {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} to check whether the device supports * flash strength control or not. * If the values of android.flash.info.singleStrengthMaxLevel and - * android.flash.info.torchStrengthMaxLevel are greater than 1, + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1, * then the device supports manual flash strength control.</p> * <p>If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> TORCH the value must be >= 1 - * and <= android.flash.info.torchStrengthMaxLevel. + * and <= {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}. * If the application doesn't set the key and - * android.flash.info.torchStrengthMaxLevel > 1, + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} > 1, * then the flash will be fired at the default level set by HAL in - * android.flash.info.torchStrengthDefaultLevel. + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL android.flash.torchStrengthDefaultLevel}. * If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> SINGLE, then the value must be >= 1 - * and <= android.flash.info.singleStrengthMaxLevel. + * and <= {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}. * If the application does not set this key and - * android.flash.info.singleStrengthMaxLevel > 1, + * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} > 1, * then the flash will be fired at the default level set by HAL - * in android.flash.info.singleStrengthDefaultLevel. + * in {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL android.flash.singleStrengthDefaultLevel}. * If {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to any of ON_AUTO_FLASH, ON_ALWAYS_FLASH, * ON_AUTO_FLASH_REDEYE, ON_EXTERNAL_FLASH values, then the strengthLevel will be ignored.</p> * <p><b>Range of valid values:</b><br> - * <code>[1-android.flash.info.torchStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is + * <code>[1-{@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is * set to TORCH; - * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is + * <code>[1-{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is * set to SINGLE</p> * <p>This key is available on all devices.</p> * * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#FLASH_MODE + * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL + * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL + * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL + * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL */ @PublicKey @NonNull diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 1460515c2438..4406a419c317 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2977,35 +2977,39 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>Flash strength level to be used when manual flash control is active.</p> * <p>Flash strength level to use in capture mode i.e. when the applications control * flash with either SINGLE or TORCH mode.</p> - * <p>Use android.flash.info.singleStrengthMaxLevel and - * android.flash.info.torchStrengthMaxLevel to check whether the device supports + * <p>Use {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} to check whether the device supports * flash strength control or not. * If the values of android.flash.info.singleStrengthMaxLevel and - * android.flash.info.torchStrengthMaxLevel are greater than 1, + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1, * then the device supports manual flash strength control.</p> * <p>If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> TORCH the value must be >= 1 - * and <= android.flash.info.torchStrengthMaxLevel. + * and <= {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}. * If the application doesn't set the key and - * android.flash.info.torchStrengthMaxLevel > 1, + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} > 1, * then the flash will be fired at the default level set by HAL in - * android.flash.info.torchStrengthDefaultLevel. + * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL android.flash.torchStrengthDefaultLevel}. * If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> SINGLE, then the value must be >= 1 - * and <= android.flash.info.singleStrengthMaxLevel. + * and <= {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}. * If the application does not set this key and - * android.flash.info.singleStrengthMaxLevel > 1, + * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} > 1, * then the flash will be fired at the default level set by HAL - * in android.flash.info.singleStrengthDefaultLevel. + * in {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL android.flash.singleStrengthDefaultLevel}. * If {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to any of ON_AUTO_FLASH, ON_ALWAYS_FLASH, * ON_AUTO_FLASH_REDEYE, ON_EXTERNAL_FLASH values, then the strengthLevel will be ignored.</p> * <p><b>Range of valid values:</b><br> - * <code>[1-android.flash.info.torchStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is + * <code>[1-{@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is * set to TORCH; - * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is + * <code>[1-{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is * set to SINGLE</p> * <p>This key is available on all devices.</p> * * @see CaptureRequest#CONTROL_AE_MODE * @see CaptureRequest#FLASH_MODE + * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL + * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL + * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL + * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL */ @PublicKey @NonNull 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/IBinder.java b/core/java/android/os/IBinder.java index 91c2965c2505..c9f207cf26e8 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -305,15 +305,28 @@ public interface IBinder { /** * Interface for receiving a callback when the process hosting an IBinder * has gone away. - * + * * @see #linkToDeath */ public interface DeathRecipient { public void binderDied(); /** - * Interface for receiving a callback when the process hosting an IBinder + * The function called when the process hosting an IBinder * has gone away. + * + * This callback will be called from any binder thread like any other binder + * transaction. If the process receiving this notification is multithreaded + * then synchronization may be required because other threads may be executing + * at the same time. + * + * No locks are held in libbinder when {@link binderDied} is called. + * + * There is no need to call {@link unlinkToDeath} in the binderDied callback. + * The binder is already dead so {@link unlinkToDeath} is a no-op. + * It will be unlinked when the last local reference of that binder proxy is + * dropped. + * * @param who The IBinder that has become invalid */ default void binderDied(@NonNull IBinder who) { 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/os/UserManager.java b/core/java/android/os/UserManager.java index 80d356614932..fdce476388d3 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -5958,19 +5958,22 @@ public class UserManager { /** * Returns the string used to represent the profile associated with the given userId. This * string typically includes the profile name used by accessibility services like TalkBack. - * @hide * * @return String representing the accessibility label for the given profile user. * * @throws android.content.res.Resources.NotFoundException if the user does not have a label * defined. + * + * @see #getBadgedLabelForUser(CharSequence, UserHandle) + * + * @hide */ @UserHandleAware( requiresAnyOfPermissionsIfNotCallerProfileGroup = { Manifest.permission.MANAGE_USERS, Manifest.permission.QUERY_USERS, Manifest.permission.INTERACT_ACROSS_USERS}) - public String getProfileAccessibilityString(int userId) { + public String getProfileAccessibilityString(@UserIdInt int userId) { if (isManagedProfile(mUserId)) { DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); dpm.getResources().getString( @@ -5980,7 +5983,7 @@ public class UserManager { return getProfileAccessibilityLabel(userId); } - private String getProfileAccessibilityLabel(int userId) { + private String getProfileAccessibilityLabel(@UserIdInt int userId) { try { final int resourceId = mService.getProfileAccessibilityLabelResId(userId); return Resources.getSystem().getString(resourceId); diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 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/provider/Settings.java b/core/java/android/provider/Settings.java index a409537a57d5..f357ebff9878 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1736,24 +1736,6 @@ public final class Settings { "android.settings.NETWORK_OPERATOR_SETTINGS"; /** - * Activity Action: Show settings for selecting the network provider. - * <p> - * In some cases, a matching Activity may not be provided, so ensure you - * safeguard against this. - * <p> - * Access to this preference can be customized via Settings' app. - * <p> - * Input: Nothing. - * <p> - * Output: Nothing. - * - * @hide - */ - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_NETWORK_PROVIDER_SETTINGS = - "android.settings.NETWORK_PROVIDER_SETTINGS"; - - /** * Activity Action: Show settings for selection of 2G/3G. * <p> * In some cases, a matching Activity may not exist, so ensure you 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/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 71066ac7ac39..3f9c819cd62f 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1276,13 +1276,22 @@ public class DreamService extends Service implements Window.Callback { }); } + /** + * Whether or not wake requests will be redirected. + * + * @hide + */ + public boolean getRedirectWake() { + return mOverlayConnection != null && mRedirectWake; + } + private void wakeUp(boolean fromSystem) { if (mDebug) { Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking + ", mFinished=" + mFinished); } - if (!fromSystem && mOverlayConnection != null && mRedirectWake) { + if (!fromSystem && getRedirectWake()) { mOverlayConnection.addConsumer(overlay -> { try { overlay.onWakeRequested(); diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java index dc41b70d1683..952c63b936c7 100644 --- a/core/java/android/view/ContentRecordingSession.java +++ b/core/java/android/view/ContentRecordingSession.java @@ -58,6 +58,15 @@ public final class ContentRecordingSession implements Parcelable { /** Can't report (e.g. side loaded app). */ public static final int TARGET_UID_UNKNOWN = -2; + /** Task id is not set either because full screen capture or launching a new app */ + public static final int TASK_ID_UNKNOWN = -1; + + /** + * Id of Task that is launched to be captured for a single app capture session. The value may be + * {@link #TASK_ID_UNKNOWN} if the session is not for a single app capture. + */ + private int mTaskId = TASK_ID_UNKNOWN; + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. @@ -115,16 +124,16 @@ public final class ContentRecordingSession implements Parcelable { /** Returns an instance initialized for task recording. */ public static ContentRecordingSession createTaskSession( @NonNull IBinder taskWindowContainerToken) { - return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN); + return createTaskSession(taskWindowContainerToken, TASK_ID_UNKNOWN); } /** Returns an instance initialized for task recording. */ public static ContentRecordingSession createTaskSession( - @NonNull IBinder taskWindowContainerToken, int targetUid) { + @NonNull IBinder taskWindowContainerToken, int taskId) { return new ContentRecordingSession() .setContentToRecord(RECORD_CONTENT_TASK) .setTokenToRecord(taskWindowContainerToken) - .setTargetUid(targetUid); + .setTaskId(taskId); } /** @@ -211,12 +220,14 @@ public final class ContentRecordingSession implements Parcelable { @DataClass.Generated.Member /* package-private */ ContentRecordingSession( + int taskId, int virtualDisplayId, @RecordContent int contentToRecord, int displayToRecord, @Nullable IBinder tokenToRecord, boolean waitingForConsent, int targetUid) { + this.mTaskId = taskId; this.mVirtualDisplayId = virtualDisplayId; this.mContentToRecord = contentToRecord; @@ -237,6 +248,15 @@ public final class ContentRecordingSession implements Parcelable { } /** + * Id of Task that is launched to be captured for a single app capture session. The value may be + * {@link #TASK_ID_UNKNOWN} if the session is not for a single app capture. + */ + @DataClass.Generated.Member + public int getTaskId() { + return mTaskId; + } + + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. */ @@ -295,6 +315,16 @@ public final class ContentRecordingSession implements Parcelable { } /** + * Id of Task that is launched to be captured for a single app capture session. The value may be + * {@link #TASK_ID_UNKNOWN} if the session is not for a single app capture. + */ + @DataClass.Generated.Member + public @NonNull ContentRecordingSession setTaskId( int value) { + mTaskId = value; + return this; + } + + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. */ @@ -374,6 +404,7 @@ public final class ContentRecordingSession implements Parcelable { // String fieldNameToString() { ... } return "ContentRecordingSession { " + + "taskId = " + mTaskId + ", " + "virtualDisplayId = " + mVirtualDisplayId + ", " + "contentToRecord = " + recordContentToString(mContentToRecord) + ", " + "displayToRecord = " + mDisplayToRecord + ", " + @@ -396,6 +427,7 @@ public final class ContentRecordingSession implements Parcelable { ContentRecordingSession that = (ContentRecordingSession) o; //noinspection PointlessBooleanExpression return true + && mTaskId == that.mTaskId && mVirtualDisplayId == that.mVirtualDisplayId && mContentToRecord == that.mContentToRecord && mDisplayToRecord == that.mDisplayToRecord @@ -411,6 +443,7 @@ public final class ContentRecordingSession implements Parcelable { // int fieldNameHashCode() { ... } int _hash = 1; + _hash = 31 * _hash + mTaskId; _hash = 31 * _hash + mVirtualDisplayId; _hash = 31 * _hash + mContentToRecord; _hash = 31 * _hash + mDisplayToRecord; @@ -427,9 +460,10 @@ public final class ContentRecordingSession implements Parcelable { // void parcelFieldName(Parcel dest, int flags) { ... } byte flg = 0; - if (mWaitingForConsent) flg |= 0x10; - if (mTokenToRecord != null) flg |= 0x8; + if (mWaitingForConsent) flg |= 0x20; + if (mTokenToRecord != null) flg |= 0x10; dest.writeByte(flg); + dest.writeInt(mTaskId); dest.writeInt(mVirtualDisplayId); dest.writeInt(mContentToRecord); dest.writeInt(mDisplayToRecord); @@ -449,13 +483,15 @@ public final class ContentRecordingSession implements Parcelable { // static FieldType unparcelFieldName(Parcel in) { ... } byte flg = in.readByte(); - boolean waitingForConsent = (flg & 0x10) != 0; + boolean waitingForConsent = (flg & 0x20) != 0; + int taskId = in.readInt(); int virtualDisplayId = in.readInt(); int contentToRecord = in.readInt(); int displayToRecord = in.readInt(); - IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder(); + IBinder tokenToRecord = (flg & 0x10) == 0 ? null : (IBinder) in.readStrongBinder(); int targetUid = in.readInt(); + this.mTaskId = taskId; this.mVirtualDisplayId = virtualDisplayId; this.mContentToRecord = contentToRecord; @@ -496,6 +532,7 @@ public final class ContentRecordingSession implements Parcelable { @DataClass.Generated.Member public static final class Builder { + private int mTaskId; private int mVirtualDisplayId; private @RecordContent int mContentToRecord; private int mDisplayToRecord; @@ -509,13 +546,25 @@ public final class ContentRecordingSession implements Parcelable { } /** + * Id of Task that is launched to be captured for a single app capture session. The value may be + * {@link #TASK_ID_UNKNOWN} if the session is not for a single app capture. + */ + @DataClass.Generated.Member + public @NonNull Builder setTaskId(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mTaskId = value; + return this; + } + + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. */ @DataClass.Generated.Member public @NonNull Builder setVirtualDisplayId(int value) { checkNotUsed(); - mBuilderFieldsSet |= 0x1; + mBuilderFieldsSet |= 0x2; mVirtualDisplayId = value; return this; } @@ -526,7 +575,7 @@ public final class ContentRecordingSession implements Parcelable { @DataClass.Generated.Member public @NonNull Builder setContentToRecord(@RecordContent int value) { checkNotUsed(); - mBuilderFieldsSet |= 0x2; + mBuilderFieldsSet |= 0x4; mContentToRecord = value; return this; } @@ -540,7 +589,7 @@ public final class ContentRecordingSession implements Parcelable { @DataClass.Generated.Member public @NonNull Builder setDisplayToRecord(int value) { checkNotUsed(); - mBuilderFieldsSet |= 0x4; + mBuilderFieldsSet |= 0x8; mDisplayToRecord = value; return this; } @@ -554,7 +603,7 @@ public final class ContentRecordingSession implements Parcelable { @DataClass.Generated.Member public @NonNull Builder setTokenToRecord(@NonNull IBinder value) { checkNotUsed(); - mBuilderFieldsSet |= 0x8; + mBuilderFieldsSet |= 0x10; mTokenToRecord = value; return this; } @@ -568,7 +617,7 @@ public final class ContentRecordingSession implements Parcelable { @DataClass.Generated.Member public @NonNull Builder setWaitingForConsent(boolean value) { checkNotUsed(); - mBuilderFieldsSet |= 0x10; + mBuilderFieldsSet |= 0x20; mWaitingForConsent = value; return this; } @@ -579,7 +628,7 @@ public final class ContentRecordingSession implements Parcelable { @DataClass.Generated.Member public @NonNull Builder setTargetUid(int value) { checkNotUsed(); - mBuilderFieldsSet |= 0x20; + mBuilderFieldsSet |= 0x40; mTargetUid = value; return this; } @@ -587,27 +636,31 @@ public final class ContentRecordingSession implements Parcelable { /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull ContentRecordingSession build() { checkNotUsed(); - mBuilderFieldsSet |= 0x40; // Mark builder used + mBuilderFieldsSet |= 0x80; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { - mVirtualDisplayId = INVALID_DISPLAY; + mTaskId = TASK_ID_UNKNOWN; } if ((mBuilderFieldsSet & 0x2) == 0) { - mContentToRecord = RECORD_CONTENT_DISPLAY; + mVirtualDisplayId = INVALID_DISPLAY; } if ((mBuilderFieldsSet & 0x4) == 0) { - mDisplayToRecord = INVALID_DISPLAY; + mContentToRecord = RECORD_CONTENT_DISPLAY; } if ((mBuilderFieldsSet & 0x8) == 0) { - mTokenToRecord = null; + mDisplayToRecord = INVALID_DISPLAY; } if ((mBuilderFieldsSet & 0x10) == 0) { - mWaitingForConsent = false; + mTokenToRecord = null; } if ((mBuilderFieldsSet & 0x20) == 0) { + mWaitingForConsent = false; + } + if ((mBuilderFieldsSet & 0x40) == 0) { mTargetUid = TARGET_UID_UNKNOWN; } ContentRecordingSession o = new ContentRecordingSession( + mTaskId, mVirtualDisplayId, mContentToRecord, mDisplayToRecord, @@ -618,7 +671,7 @@ public final class ContentRecordingSession implements Parcelable { } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x40) != 0) { + if ((mBuilderFieldsSet & 0x80) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -626,10 +679,10 @@ public final class ContentRecordingSession implements Parcelable { } @DataClass.Generated( - time = 1697456140720L, + time = 1716481148184L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java", - inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") + inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\npublic static final int TASK_ID_UNKNOWN\nprivate int mTaskId\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index beb4d95263be..57d1b8d18fe1 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -227,7 +227,10 @@ public class HandwritingInitiator { mState.mStylusDownY, /* isHover */ false); if (candidateView != null && candidateView.isEnabled()) { boolean candidateHasFocus = candidateView.hasFocus(); - if (shouldShowHandwritingUnavailableMessageForView(candidateView)) { + if (!candidateView.isStylusHandwritingAvailable()) { + mState.mShouldInitHandwriting = false; + return false; + } else if (shouldShowHandwritingUnavailableMessageForView(candidateView)) { int messagesResId = (candidateView instanceof TextView tv && tv.isAnyPasswordInputType()) ? R.string.error_handwriting_unsupported_password 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/InsetsFlags.java b/core/java/android/view/InsetsFlags.java index ca8a7a8cf175..2fa57688f0cb 100644 --- a/core/java/android/view/InsetsFlags.java +++ b/core/java/android/view/InsetsFlags.java @@ -17,6 +17,7 @@ package android.view; import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -24,6 +25,7 @@ import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_B import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS; +import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND; import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; @@ -69,7 +71,15 @@ public class InsetsFlags { @ViewDebug.FlagToString( mask = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS, equals = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS, - name = "FORCE_LIGHT_NAVIGATION_BARS") + name = "FORCE_LIGHT_NAVIGATION_BARS"), + @ViewDebug.FlagToString( + mask = APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + equals = APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + name = "APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND"), + @ViewDebug.FlagToString( + mask = APPEARANCE_LIGHT_CAPTION_BARS, + equals = APPEARANCE_LIGHT_CAPTION_BARS, + name = "APPEARANCE_LIGHT_CAPTION_BARS") }) public @Appearance int appearance; diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 4e5cb58a00b5..588e9e0a3b12 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -269,22 +269,54 @@ public class InsetsSourceControl implements Parcelable { public Array() { } - public Array(@NonNull Array other) { - mControls = other.mControls; + /** + * @param copyControls whether or not to make a copy of the each {@link InsetsSourceControl} + */ + public Array(@NonNull Array other, boolean copyControls) { + setTo(other, copyControls); } - public Array(Parcel in) { + public Array(@NonNull Parcel in) { readFromParcel(in); } - public void set(@Nullable InsetsSourceControl[] controls) { - mControls = controls; + /** Updates the current Array to the given Array. */ + public void setTo(@NonNull Array other, boolean copyControls) { + set(other.mControls, copyControls); } + /** Updates the current controls to the given controls. */ + public void set(@Nullable InsetsSourceControl[] controls, boolean copyControls) { + if (controls == null || !copyControls) { + mControls = controls; + return; + } + // Make a copy of the array. + mControls = new InsetsSourceControl[controls.length]; + for (int i = mControls.length - 1; i >= 0; i--) { + if (controls[i] != null) { + mControls[i] = new InsetsSourceControl(controls[i]); + } + } + } + + /** Gets the controls. */ public @Nullable InsetsSourceControl[] get() { return mControls; } + /** Sets the given flags to all controls. */ + public void setParcelableFlags(int parcelableFlags) { + if (mControls == null) { + return; + } + for (InsetsSourceControl control : mControls) { + if (control != null) { + control.setParcelableFlags(parcelableFlags); + } + } + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java index 118b03ce5504..3f6fd646994c 100644 --- a/core/java/android/view/KeyboardShortcutInfo.java +++ b/core/java/android/view/KeyboardShortcutInfo.java @@ -28,8 +28,8 @@ import android.os.Parcelable; * Information about a Keyboard Shortcut. */ public final class KeyboardShortcutInfo implements Parcelable { - private final CharSequence mLabel; - private final Icon mIcon; + @Nullable private final CharSequence mLabel; + @Nullable private Icon mIcon; private final char mBaseCharacter; private final int mKeycode; private final int mModifiers; @@ -116,6 +116,15 @@ public final class KeyboardShortcutInfo implements Parcelable { } /** + * Removes an icon that was previously set. + * + * @hide + */ + public void clearIcon() { + mIcon = null; + } + + /** * Returns the base keycode that, combined with the modifiers, triggers this shortcut. If the * base character was set instead, returns {@link KeyEvent#KEYCODE_UNKNOWN}. Valid keycodes are * defined as constants in {@link KeyEvent}. @@ -165,4 +174,4 @@ public final class KeyboardShortcutInfo implements Parcelable { return new KeyboardShortcutInfo[size]; } }; -}
\ No newline at end of file +} diff --git a/core/java/android/view/NativeVectorDrawableAnimator.java b/core/java/android/view/NativeVectorDrawableAnimator.java index b0556a3f8a91..e92bd1f5d6b8 100644 --- a/core/java/android/view/NativeVectorDrawableAnimator.java +++ b/core/java/android/view/NativeVectorDrawableAnimator.java @@ -16,6 +16,8 @@ package android.view; +import android.animation.Animator; + /** * Exists just to allow for android.graphics & android.view package separation * @@ -26,4 +28,7 @@ package android.view; public interface NativeVectorDrawableAnimator { /** @hide */ long getAnimatorNativePtr(); + + /** @hide */ + void setThreadedRendererAnimatorListener(Animator.AnimatorListener listener); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 1cd7d349a9af..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, @@ -302,6 +303,7 @@ public final class SurfaceControl implements Parcelable { long desiredPresentTimeNanos); private static native void nativeSetFrameTimeline(long transactionObj, long vsyncId); + private static native void nativeNotifyShutdown(); /** * Transforms that can be applied to buffers as they are displayed to a window. @@ -4303,13 +4305,37 @@ 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; } @@ -4765,4 +4791,11 @@ public final class SurfaceControl implements Parcelable { return nativeGetStalledTransactionInfo(pid); } + /** + * Notify the SurfaceFlinger to capture transaction traces when shutdown. + * @hide + */ + public static void notifyShutdown() { + nativeNotifyShutdown(); + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1cb276568244..95c9d7bb72c5 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -23705,12 +23705,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; - // // For VRR to vote the preferred frame rate - if (sToolkitSetFrameRateReadOnlyFlagValue - && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { - votePreferredFrameRate(); - } - mPrivateFlags4 |= PFLAG4_HAS_DRAWN; // Fast path for layouts with no backgrounds @@ -23727,6 +23721,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, draw(canvas); } } + + // For VRR to vote the preferred frame rate + if (sToolkitSetFrameRateReadOnlyFlagValue + && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { + votePreferredFrameRate(); + } } finally { renderNode.endRecording(); setDisplayListProperties(renderNode); diff --git a/core/java/android/view/ViewAnimationHostBridge.java b/core/java/android/view/ViewAnimationHostBridge.java index e0fae21bbdf6..62b2b6c053c4 100644 --- a/core/java/android/view/ViewAnimationHostBridge.java +++ b/core/java/android/view/ViewAnimationHostBridge.java @@ -16,14 +16,19 @@ package android.view; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.graphics.RenderNode; +import androidx.annotation.NonNull; + /** * Maps a View to a RenderNode's AnimationHost * * @hide */ -public class ViewAnimationHostBridge implements RenderNode.AnimationHost { +public class ViewAnimationHostBridge extends AnimatorListenerAdapter + implements RenderNode.AnimationHost { private final View mView; /** @@ -34,17 +39,35 @@ public class ViewAnimationHostBridge implements RenderNode.AnimationHost { } @Override - public void registerAnimatingRenderNode(RenderNode animator) { - mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(animator); + public void registerAnimatingRenderNode(RenderNode renderNode, Animator animator) { + mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(renderNode); + animator.addListener(this); } @Override public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator) { mView.mAttachInfo.mViewRootImpl.registerVectorDrawableAnimator(animator); + animator.setThreadedRendererAnimatorListener(this); } @Override public boolean isAttached() { return mView.mAttachInfo != null; } + + @Override + public void onAnimationStart(@NonNull Animator animation) { + ViewRootImpl viewRoot = mView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.addThreadedRendererView(mView); + } + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + ViewRootImpl viewRoot = mView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.removeThreadedRendererView(mView); + } + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8e2b7f1d4dd2..b54e052cf538 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -143,7 +143,7 @@ import android.app.ICompatCameraControlCallback; import android.app.ResourcesManager; import android.app.WindowConfiguration; import android.app.compat.CompatChanges; -import android.app.servertransaction.WindowStateResizeItem; +import android.app.servertransaction.WindowStateTransactionItem; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ClipDescription; @@ -427,6 +427,12 @@ public final class ViewRootImpl implements ViewParent, private static final long NANOS_PER_SEC = 1000000000; + // If the ViewRootImpl has been idle for more than 750ms, clear the preferred + // frame rate category and frame rate. + private static final int IDLE_TIME_MILLIS = 750; + + private static final long NANOS_PER_MILLI = 1_000_000; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); @@ -659,6 +665,10 @@ public final class ViewRootImpl implements ViewParent, private int mMinusOneFrameIntervalMillis = 0; // VRR interval between the previous and the frame before private int mMinusTwoFrameIntervalMillis = 0; + // VRR has the invalidation idle message been posted? + private boolean mInvalidationIdleMessagePosted = false; + // VRR: List of all Views that are animating with the threaded render + private ArrayList<View> mThreadedRendererViews = new ArrayList(); /** * Update the Choreographer's FrameInfo object with the timing information for the current @@ -1184,6 +1194,8 @@ public final class ViewRootImpl implements ViewParent, toolkitFrameRateVelocityMappingReadOnly(); private static boolean sToolkitEnableInvalidateCheckThreadFlagValue = Flags.enableInvalidateCheckThread(); + private static boolean sSurfaceFlingerBugfixFlagValue = + com.android.graphics.surfaceflinger.flags.Flags.vrrBugfix24q4(); static { sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); @@ -2264,6 +2276,33 @@ public final class ViewRootImpl implements ViewParent, requestLayout(); } + /** Handles messages {@link #MSG_INSETS_CONTROL_CHANGED}. */ + private void handleInsetsControlChanged(@NonNull InsetsState insetsState, + @NonNull InsetsSourceControl.Array activeControls) { + final InsetsSourceControl[] controls = activeControls.get(); + + if (mTranslator != null) { + mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); + mTranslator.translateSourceControlsInScreenToAppWindow(controls); + } + + // Deliver state change before control change, such that: + // a) When gaining control, controller can compare with server state to evaluate + // whether it needs to run animation. + // b) When loosing control, controller can restore server state by taking last + // dispatched state as truth. + mInsetsController.onStateChanged(insetsState); + if (mAdded) { + mInsetsController.onControlsChanged(controls); + } else if (controls != null) { + for (InsetsSourceControl control : controls) { + if (control != null) { + control.release(SurfaceControl::release); + } + } + } + } + private final DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayChanged(int displayId) { @@ -4261,8 +4300,13 @@ public final class ViewRootImpl implements ViewParent, // when the values are applicable. if (mDrawnThisFrame) { mDrawnThisFrame = false; + if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); + } setCategoryFromCategoryCounts(); updateInfrequentCount(); + updateFrameRateFromThreadedRendererViews(); setPreferredFrameRate(mPreferredFrameRate); setPreferredFrameRateCategory(mPreferredFrameRateCategory); if (mPreferredFrameRate > 0 @@ -6499,6 +6543,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_WINDOW_TOUCH_MODE_CHANGED"; case MSG_KEEP_CLEAR_RECTS_CHANGED: return "MSG_KEEP_CLEAR_RECTS_CHANGED"; + case MSG_CHECK_INVALIDATION_IDLE: + return "MSG_CHECK_INVALIDATION_IDLE"; case MSG_REFRESH_POINTER_ICON: return "MSG_REFRESH_POINTER_ICON"; case MSG_TOUCH_BOOST_TIMEOUT: @@ -6572,24 +6618,11 @@ public final class ViewRootImpl implements ViewParent, break; } case MSG_INSETS_CONTROL_CHANGED: { - SomeArgs args = (SomeArgs) msg.obj; - - // Deliver state change before control change, such that: - // a) When gaining control, controller can compare with server state to evaluate - // whether it needs to run animation. - // b) When loosing control, controller can restore server state by taking last - // dispatched state as truth. - mInsetsController.onStateChanged((InsetsState) args.arg1); - InsetsSourceControl[] controls = (InsetsSourceControl[]) args.arg2; - if (mAdded) { - mInsetsController.onControlsChanged(controls); - } else if (controls != null) { - for (InsetsSourceControl control : controls) { - if (control != null) { - control.release(SurfaceControl::release); - } - } - } + final SomeArgs args = (SomeArgs) msg.obj; + final InsetsState insetsState = (InsetsState) args.arg1; + final InsetsSourceControl.Array activeControls = + (InsetsSourceControl.Array) args.arg2; + handleInsetsControlChanged(insetsState, activeControls); args.recycle(); break; } @@ -6759,6 +6792,31 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; + case MSG_CHECK_INVALIDATION_IDLE: { + long delta; + if (mIsTouchBoosting || mIsFrameRateBoosting || mInsetsAnimationRunning) { + delta = 0; + } else { + delta = System.nanoTime() / NANOS_PER_MILLI - mLastUpdateTimeMillis; + } + if (delta >= IDLE_TIME_MILLIS) { + mFrameRateCategoryHighCount = 0; + mFrameRateCategoryHighHintCount = 0; + mFrameRateCategoryNormalCount = 0; + mFrameRateCategoryLowCount = 0; + mPreferredFrameRate = 0; + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + updateFrameRateFromThreadedRendererViews(); + setPreferredFrameRate(mPreferredFrameRate); + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mInvalidationIdleMessagePosted = false; + } else { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + IDLE_TIME_MILLIS - delta); + } + break; + } case MSG_TOUCH_BOOST_TIMEOUT: /** * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). @@ -7273,7 +7331,8 @@ public final class ViewRootImpl implements ViewParent, if (dispatcher.isBackGestureInProgress()) { return FINISH_NOT_HANDLED; } - if (topCallback instanceof OnBackAnimationCallback) { + if (topCallback instanceof OnBackAnimationCallback + && !(topCallback instanceof ImeBackAnimationController)) { final OnBackAnimationCallback animationCallback = (OnBackAnimationCallback) topCallback; switch (keyEvent.getAction()) { @@ -9783,25 +9842,9 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } - private void dispatchInsetsControlChanged(InsetsState insetsState, - InsetsSourceControl[] activeControls) { - if (Binder.getCallingPid() == android.os.Process.myPid()) { - insetsState = new InsetsState(insetsState, true /* copySource */); - if (activeControls != null) { - for (int i = activeControls.length - 1; i >= 0; i--) { - activeControls[i] = new InsetsSourceControl(activeControls[i]); - } - } - } - if (mTranslator != null) { - mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); - mTranslator.translateSourceControlsInScreenToAppWindow(activeControls); - } - if (insetsState != null && insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) { - ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged", - getInsetsController().getHost().getInputMethodManager(), null /* icProto */); - } - SomeArgs args = SomeArgs.obtain(); + private void dispatchInsetsControlChanged(@NonNull InsetsState insetsState, + @NonNull InsetsSourceControl.Array activeControls) { + final SomeArgs args = SomeArgs.obtain(); args.arg1 = insetsState; args.arg2 = activeControls; mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget(); @@ -11201,10 +11244,10 @@ public final class ViewRootImpl implements ViewParent, } } - static class W extends IWindow.Stub implements WindowStateResizeItem.ResizeListener { + static class W extends IWindow.Stub implements WindowStateTransactionItem.TransactionListener { private final WeakReference<ViewRootImpl> mViewAncestor; private final IWindowSession mWindowSession; - private boolean mIsFromResizeItem; + private boolean mIsFromTransactionItem; W(ViewRootImpl viewAncestor) { mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor); @@ -11212,8 +11255,8 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void onExecutingWindowStateResizeItem() { - mIsFromResizeItem = true; + public void onExecutingWindowStateTransactionItem() { + mIsFromTransactionItem = true; } @Override @@ -11221,8 +11264,8 @@ public final class ViewRootImpl implements ViewParent, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing, @Nullable ActivityWindowInfo activityWindowInfo) { - final boolean isFromResizeItem = mIsFromResizeItem; - mIsFromResizeItem = false; + final boolean isFromResizeItem = mIsFromTransactionItem; + mIsFromTransactionItem = false; // Although this is a AIDL method, it will only be triggered in local process through // either WindowStateResizeItem or WindowlessWindowManager. final ViewRootImpl viewAncestor = mViewAncestor.get(); @@ -11244,9 +11287,9 @@ public final class ViewRootImpl implements ViewParent, return; } // The the parameters from WindowStateResizeItem are already copied. - final boolean needCopy = + final boolean needsCopy = !isFromResizeItem && (Binder.getCallingPid() == Process.myPid()); - if (needCopy) { + if (needsCopy) { insetsState = new InsetsState(insetsState, true /* copySource */); frames = new ClientWindowFrames(frames); mergedConfiguration = new MergedConfiguration(mergedConfiguration); @@ -11259,10 +11302,35 @@ public final class ViewRootImpl implements ViewParent, @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl.Array activeControls) { + final boolean isFromInsetsControlChangeItem = mIsFromTransactionItem; + mIsFromTransactionItem = false; final ViewRootImpl viewAncestor = mViewAncestor.get(); - if (viewAncestor != null) { - viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls.get()); + if (viewAncestor == null) { + return; + } + if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) { + ImeTracing.getInstance().triggerClientDump( + "ViewRootImpl#dispatchInsetsControlChanged", + viewAncestor.getInsetsController().getHost().getInputMethodManager(), + null /* icProto */); } + // If the UI thread is the same as the current thread that is dispatching + // WindowStateInsetsControlChangeItem, then it can run directly. + if (isFromInsetsControlChangeItem && viewAncestor.mHandler.getLooper() + == ActivityThread.currentActivityThread().getLooper()) { + viewAncestor.handleInsetsControlChanged(insetsState, activeControls); + return; + } + // The parameters from WindowStateInsetsControlChangeItem are already copied. + final boolean needsCopy = + !isFromInsetsControlChangeItem && (Binder.getCallingPid() == Process.myPid()); + if (needsCopy) { + insetsState = new InsetsState(insetsState, true /* copySource */); + activeControls = new InsetsSourceControl.Array( + activeControls, true /* copyControls */); + } + + viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls); } @Override @@ -12577,6 +12645,24 @@ public final class ViewRootImpl implements ViewParent, } /** + * Views that are animating with the ThreadedRenderer don't use the normal invalidation + * path, so the value won't be updated through performTraversals. This reads the votes + * from those views. + */ + private void updateFrameRateFromThreadedRendererViews() { + ArrayList<View> views = mThreadedRendererViews; + for (int i = views.size() - 1; i >= 0; i--) { + View view = views.get(i); + View.AttachInfo attachInfo = view.mAttachInfo; + if (attachInfo == null || attachInfo.mViewRootImpl != this) { + views.remove(i); + } else { + view.votePreferredFrameRate(); + } + } + } + + /** * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts. */ private void setCategoryFromCategoryCounts() { @@ -12757,6 +12843,31 @@ public final class ViewRootImpl implements ViewParent, } /** + * Mark a View as having an active ThreadedRenderer animation. This is used for + * RenderNodeAnimators and AnimatedVectorDrawables. When the animation stops, + * {@link #removeThreadedRendererView(View)} must be called. + * @param view The View with the ThreadedRenderer animation that started. + */ + public void addThreadedRendererView(View view) { + if (!mThreadedRendererViews.contains(view)) { + mThreadedRendererViews.add(view); + } + } + + /** + * When a ThreadedRenderer animation ends, the View that is associated with it using + * {@link #addThreadedRendererView(View)} must be removed with a call to this method. + * @param view The View whose ThreadedRender animation has stopped. + */ + public void removeThreadedRendererView(View view) { + mThreadedRendererViews.remove(view); + if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); + } + } + + /** * Returns {@link #INTERMITTENT_STATE_INTERMITTENT} when the ViewRootImpl has only been * updated intermittently, {@link #INTERMITTENT_STATE_NOT_INTERMITTENT} when it is * not updated intermittently, and {@link #INTERMITTENT_STATE_IN_TRANSITION} when it @@ -12979,6 +13090,10 @@ public final class ViewRootImpl implements ViewParent, private void removeVrrMessages() { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); + if (mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = false; + mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); + } } /** @@ -12997,7 +13112,7 @@ public final class ViewRootImpl implements ViewParent, mMinusOneFrameIntervalMillis = timeIntervalMillis; mLastUpdateTimeMillis = currentTimeMillis; - if (timeIntervalMillis + mMinusTwoFrameIntervalMillis + if (mThreadedRendererViews.isEmpty() && timeIntervalMillis + mMinusTwoFrameIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { int infrequentUpdateCount = mInfrequentUpdateCount; mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 86e5bea46882..1af9387e6fbd 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -90,6 +90,19 @@ public abstract class ViewStructure { public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE"; + + /** + * Key used for specifying the version of the view that generated the virtual structure for + * itself and its children + * + * For example, if the virtual structure is generated by a webview of version "104.0.5112.69", + * then the value should be "104.0.5112.69" + * + * @hide + */ + public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = + "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER"; + /** * Set the identifier for this view. * diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index 07d6acbe38a8..c79eac605e64 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -132,7 +132,8 @@ public abstract class RemoteViewsService extends Service { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); Parcel capSizeTestParcel = Parcel.obtain(); - capSizeTestParcel.allowSquashing(); + // restore allowSquashing to reduce the noise in error messages + boolean prevAllowSquashing = capSizeTestParcel.allowSquashing(); try { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = @@ -154,6 +155,7 @@ public abstract class RemoteViewsService extends Service { items = itemsBuilder.build(); } finally { + capSizeTestParcel.restoreAllowSquashing(prevAllowSquashing); // Recycle the parcel capSizeTestParcel.recycle(); } diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java index 8d71a8e998bd..9cd2a716e498 100644 --- a/core/java/android/window/DisplayWindowPolicyController.java +++ b/core/java/android/window/DisplayWindowPolicyController.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.WindowConfiguration; +import android.companion.virtualdevice.flags.Flags; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -66,6 +67,9 @@ public abstract class DisplayWindowPolicyController { public DisplayWindowPolicyController() { synchronized (mSupportedWindowingModes) { mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + if (Flags.virtualDisplayMultiWindowModeSupport()) { + mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); + } } } diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl index 5ba2f6caac2d..6f4dd4e3c5ed 100644 --- a/core/java/android/window/IWindowOrganizerController.aidl +++ b/core/java/android/window/IWindowOrganizerController.aidl @@ -93,11 +93,17 @@ interface IWindowOrganizerController { ITaskFragmentOrganizerController getTaskFragmentOrganizerController(); /** - * Registers a transition player with Core. There is only one of these at a time and calling - * this will replace the existing one if set. + * Registers a transition player with Core. There is only one of these active at a time so + * calling this will replace the existing one (if set) until it is unregistered. */ void registerTransitionPlayer(in ITransitionPlayer player); + /** + * Un-registers a transition player from Core. This will restore whichever player was active + * prior to registering this one. + */ + void unregisterTransitionPlayer(in ITransitionPlayer player); + /** @return An interface enabling the transition players to report its metrics. */ ITransitionMetricsReporter getTransitionMetricsReporter(); diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 29bb32e6443f..4c8bad6d0aff 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -50,7 +50,6 @@ import android.app.ActivityManager; import android.app.ActivityThread; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.GraphicBuffer; import android.graphics.Paint; import android.graphics.PixelFormat; @@ -68,6 +67,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.DecorView; +import com.android.window.flags.Flags; /** * Utils class to help draw a snapshot on a surface. @@ -77,6 +77,14 @@ public class SnapshotDrawerUtils { private static final String TAG = "SnapshotDrawerUtils"; /** + * Used to check if toolkitSetFrameRateReadOnly flag is enabled + * + * @hide + */ + private static boolean sToolkitSetFrameRateReadOnlyFlagValue = + android.view.flags.Flags.toolkitSetFrameRateReadOnly(); + + /** * When creating the starting window, we use the exact same layout flags such that we end up * with a window with the exact same dimensions etc. However, these flags are not used in layout * and might cause other side effects so we exclude them. @@ -181,7 +189,8 @@ public class SnapshotDrawerUtils { // We consider nearly matched dimensions as there can be rounding errors and the user // won't notice very minute differences from scaling one dimension more than the other - boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshotW, mSnapshotH); + boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshotW, mSnapshotH) + && !Flags.drawSnapshotAspectRatioMatch(); // Keep a reference to it such that it doesn't get destroyed when finalized. SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session) @@ -382,8 +391,8 @@ public class SnapshotDrawerUtils { } final SnapshotSurface drawSurface = new SnapshotSurface( rootSurface, snapshot, lp.getTitle()); - - final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; + final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() + ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; final ActivityManager.TaskDescription taskDescription = getOrCreateTaskDescription(runningTaskInfo); @@ -400,7 +409,8 @@ public class SnapshotDrawerUtils { public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info, CharSequence title, @WindowManager.LayoutParams.WindowType int windowType, int pixelFormat, IBinder token) { - final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; + final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() + ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) { @@ -437,6 +447,9 @@ public class SnapshotDrawerUtils { layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes()); layoutParams.setFitInsetsSides(attrs.getFitInsetsSides()); layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility()); + if (sToolkitSetFrameRateReadOnlyFlagValue) { + layoutParams.setFrameRatePowerSavingsBalanced(false); + } layoutParams.setTitle(title); layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL; @@ -527,7 +540,7 @@ public class SnapshotDrawerUtils { void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, int statusBarHeight) { - if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 + if (statusBarHeight > 0 && alpha(mStatusBarColor) != 0 && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { final int rightInset = (int) (mSystemBarInsets.right * mScale); final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; @@ -541,7 +554,7 @@ public class SnapshotDrawerUtils { getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, mScale); final boolean visible = isNavigationBarColorViewVisible(); - if (visible && Color.alpha(mNavigationBarColor) != 0 + if (visible && alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { c.drawRect(navigationBarRect, mNavigationBarPaint); } diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 2dc2cbca0548..5c5da49fe543 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -155,7 +155,7 @@ public class WindowOrganizer { * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) - public void registerTransitionPlayer(@Nullable ITransitionPlayer player) { + public void registerTransitionPlayer(@NonNull ITransitionPlayer player) { try { getWindowOrganizerController().registerTransitionPlayer(player); } catch (RemoteException e) { @@ -164,6 +164,19 @@ public class WindowOrganizer { } /** + * Unregister a previously-registered ITransitionPlayer. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) + public void unregisterTransitionPlayer(@NonNull ITransitionPlayer player) { + try { + getWindowOrganizerController().unregisterTransitionPlayer(player); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @see TransitionMetrics * @hide */ diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 4b2beb903325..5b99ff9703e0 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -2,13 +2,6 @@ package: "com.android.window.flags" container: "system" flag { - name: "disable_thin_letterboxing_reachability" - namespace: "large_screen_experiences_app_compat" - description: "Whether reachability is disabled in case of thin letterboxing" - bug: "334077350" -} - -flag { name: "disable_thin_letterboxing_policy" namespace: "large_screen_experiences_app_compat" description: "Whether reachability is disabled in case of thin letterboxing" @@ -80,6 +73,16 @@ flag { } flag { + name: "immersive_app_repositioning" + namespace: "large_screen_experiences_app_compat" + description: "Fix immersive apps changing size when repositioning" + bug: "334076352" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "camera_compat_for_freeform" namespace: "large_screen_experiences_app_compat" description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index f08f5b8fddbe..d6f65f8c9d8b 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -171,4 +171,15 @@ flag { description: "Actively release task snapshot memory" bug: "238206323" is_fixed_read_only: true +} + +flag { + name: "draw_snapshot_aspect_ratio_match" + namespace: "windowing_frontend" + description: "The aspect ratio should always match when drawing snapshot" + bug: "341020277" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 6af3d9e50d75..9e69f8910bab 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -147,4 +147,25 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + namespace: "windowing_sdk" + name: "insets_control_seq" + description: "Add seqId to InsetsControls to ensure the stale update is ignored" + bug: "339380439" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "windowing_sdk" + name: "move_animation_options_to_change" + description: "Move AnimationOptions from TransitionInfo to each Change" + bug: "327332488" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 33b4e4a7dd0b..75ddb58590a1 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -103,6 +103,8 @@ public class AccessibilityShortcutController { // The component name for the sub setting of Hearing aids in Accessibility settings public static final ComponentName ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "HearingAids"); + public static final ComponentName ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "HearingDevicesTile"); public static final ComponentName COLOR_INVERSION_TILE_COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "ColorInversionTile"); diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java index c08968dabc89..6420620adda9 100644 --- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java +++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java @@ -16,6 +16,8 @@ package com.android.internal.accessibility.common; +import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; +import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME; @@ -160,6 +162,8 @@ public final class ShortcutConstants { DALTONIZER_COMPONENT_NAME, DALTONIZER_TILE_COMPONENT_NAME, ONE_HANDED_COMPONENT_NAME, ONE_HANDED_TILE_COMPONENT_NAME, REDUCE_BRIGHT_COLORS_COMPONENT_NAME, REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME, - FONT_SIZE_COMPONENT_NAME, FONT_SIZE_TILE_COMPONENT_NAME + FONT_SIZE_COMPONENT_NAME, FONT_SIZE_TILE_COMPONENT_NAME, + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME, + ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME ); } 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/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java deleted file mode 100644 index c6e8bf75dbcd..000000000000 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Copyright (C) 2015 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.internal.policy; - -import android.graphics.Insets; -import android.graphics.RecordingCanvas; -import android.graphics.Rect; -import android.graphics.RenderNode; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Looper; -import android.view.Choreographer; -import android.view.ThreadedRenderer; - -/** - * The thread which draws a fill in background while the app is resizing in areas where the app - * content draw is lagging behind the resize operation. - * It starts with the creation and it ends once someone calls destroy(). - * Any size changes can be passed by a call to setTargetRect will passed to the thread and - * executed via the Choreographer. - * @hide - */ -public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback { - - private DecorView mDecorView; - - // This is containing the last requested size by a resize command. Note that this size might - // or might not have been applied to the output already. - private final Rect mTargetRect = new Rect(); - - // The render nodes for the multi threaded renderer. - private ThreadedRenderer mRenderer; - private RenderNode mFrameAndBackdropNode; - private RenderNode mSystemBarBackgroundNode; - - private final Rect mOldTargetRect = new Rect(); - private final Rect mNewTargetRect = new Rect(); - - private Choreographer mChoreographer; - - // Cached size values from the last render for the case that the view hierarchy is gone - // during a configuration change. - private int mLastContentWidth; - private int mLastContentHeight; - private int mLastXOffset; - private int mLastYOffset; - - // Whether to report when next frame is drawn or not. - private boolean mReportNextDraw; - - private Drawable mCaptionBackgroundDrawable; - private Drawable mUserCaptionBackgroundDrawable; - private Drawable mResizingBackgroundDrawable; - private ColorDrawable mStatusBarColor; - private ColorDrawable mNavigationBarColor; - private boolean mOldFullscreen; - private boolean mFullscreen; - private final Rect mOldSystemBarInsets = new Rect(); - private final Rect mSystemBarInsets = new Rect(); - private final Rect mTmpRect = new Rect(); - - public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, - Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, - Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, - boolean fullscreen, Insets systemBarInsets) { - setName("ResizeFrame"); - - mRenderer = renderer; - onResourcesLoaded(decorView, resizingBackgroundDrawable, captionBackgroundDrawable, - userCaptionBackgroundDrawable, statusBarColor, navigationBarColor); - - // Create a render node for the content and frame backdrop - // which can be resized independently from the content. - mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null); - - mRenderer.addRenderNode(mFrameAndBackdropNode, true); - - // Set the initial bounds and draw once so that we do not get a broken frame. - mTargetRect.set(initialBounds); - mFullscreen = fullscreen; - mOldFullscreen = fullscreen; - mSystemBarInsets.set(systemBarInsets.toRect()); - mOldSystemBarInsets.set(systemBarInsets.toRect()); - - // Kick off our draw thread. - start(); - } - - void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, - Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, - int statusBarColor, int navigationBarColor) { - synchronized (this) { - mDecorView = decorView; - mResizingBackgroundDrawable = resizingBackgroundDrawable != null - && resizingBackgroundDrawable.getConstantState() != null - ? resizingBackgroundDrawable.getConstantState().newDrawable() - : null; - mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable != null - && captionBackgroundDrawableDrawable.getConstantState() != null - ? captionBackgroundDrawableDrawable.getConstantState().newDrawable() - : null; - mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable != null - && userCaptionBackgroundDrawable.getConstantState() != null - ? userCaptionBackgroundDrawable.getConstantState().newDrawable() - : null; - if (mCaptionBackgroundDrawable == null) { - mCaptionBackgroundDrawable = mResizingBackgroundDrawable; - } - if (statusBarColor != 0) { - mStatusBarColor = new ColorDrawable(statusBarColor); - addSystemBarNodeIfNeeded(); - } else { - mStatusBarColor = null; - } - if (navigationBarColor != 0) { - mNavigationBarColor = new ColorDrawable(navigationBarColor); - addSystemBarNodeIfNeeded(); - } else { - mNavigationBarColor = null; - } - } - } - - private void addSystemBarNodeIfNeeded() { - if (mSystemBarBackgroundNode != null) { - return; - } - mSystemBarBackgroundNode = RenderNode.create("SystemBarBackgroundNode", null); - mRenderer.addRenderNode(mSystemBarBackgroundNode, false); - } - - /** - * Call this function asynchronously when the window size has been changed or when the insets - * have changed or whether window switched between a fullscreen or non-fullscreen layout. - * The change will be picked up once per frame and the frame will be re-rendered accordingly. - * - * @param newTargetBounds The new target bounds. - * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemBarInsets The current visible system insets for the window. - */ - public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemBarInsets) { - synchronized (this) { - mFullscreen = fullscreen; - mTargetRect.set(newTargetBounds); - mSystemBarInsets.set(systemBarInsets); - // Notify of a bounds change. - pingRenderLocked(false /* drawImmediate */); - } - } - - /** - * The window got replaced due to a configuration change. - */ - public void onConfigurationChange() { - synchronized (this) { - if (mRenderer != null) { - // Enforce a window redraw. - mOldTargetRect.set(0, 0, 0, 0); - pingRenderLocked(false /* drawImmediate */); - } - } - } - - /** - * All resources of the renderer will be released. This function can be called from the - * the UI thread as well as the renderer thread. - */ - void releaseRenderer() { - synchronized (this) { - if (mRenderer != null) { - // Invalidate the current content bounds. - mRenderer.setContentDrawBounds(0, 0, 0, 0); - - // Remove the render node again - // (see comment above - better to do that only once). - mRenderer.removeRenderNode(mFrameAndBackdropNode); - if (mSystemBarBackgroundNode != null) { - mRenderer.removeRenderNode(mSystemBarBackgroundNode); - } - - mRenderer = null; - - // Exit the renderer loop. - pingRenderLocked(false /* drawImmediate */); - } - } - } - - @Override - public void run() { - try { - Looper.prepare(); - synchronized (this) { - if (mRenderer == null) { - // This can happen if 'releaseRenderer' is called immediately after 'start'. - return; - } - mChoreographer = Choreographer.getInstance(); - } - Looper.loop(); - } finally { - releaseRenderer(); - } - synchronized (this) { - // Make sure no more messages are being sent. - mChoreographer = null; - Choreographer.releaseInstance(); - } - } - - /** - * The implementation of the FrameCallback. - * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, - * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000} - */ - @Override - public void doFrame(long frameTimeNanos) { - synchronized (this) { - if (mRenderer == null) { - reportDrawIfNeeded(); - // Tell the looper to stop. We are done. - Looper.myLooper().quit(); - return; - } - doFrameUncheckedLocked(); - } - } - - private void doFrameUncheckedLocked() { - mNewTargetRect.set(mTargetRect); - if (!mNewTargetRect.equals(mOldTargetRect) - || mOldFullscreen != mFullscreen - || !mSystemBarInsets.equals(mOldSystemBarInsets) - || mReportNextDraw) { - mOldFullscreen = mFullscreen; - mOldTargetRect.set(mNewTargetRect); - mOldSystemBarInsets.set(mSystemBarInsets); - redrawLocked(mNewTargetRect, mFullscreen); - } - } - - /** - * The content is about to be drawn and we got the location of where it will be shown. - * If a "redrawLocked" call has already been processed, we will re-issue the call - * if the previous call was ignored since the size was unknown. - * @param xOffset The x offset where the content is drawn to. - * @param yOffset The y offset where the content is drawn to. - * @param xSize The width size of the content. This should not be 0. - * @param ySize The height of the content. - * @return true if a frame should be requested after the content is drawn; false otherwise. - */ - boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) { - synchronized (this) { - final boolean firstCall = mLastContentWidth == 0; - // The current content buffer is drawn here. - mLastContentWidth = xSize; - mLastContentHeight = ySize; - mLastXOffset = xOffset; - mLastYOffset = yOffset; - - // Inform the renderer of the content's new bounds - mRenderer.setContentDrawBounds( - mLastXOffset, - mLastYOffset, - mLastXOffset + mLastContentWidth, - mLastYOffset + mLastContentHeight); - - // If this was the first call and redrawLocked got already called prior - // to us, we should re-issue a redrawLocked now. - return firstCall; - } - } - - void onRequestDraw(boolean reportNextDraw) { - synchronized (this) { - mReportNextDraw = reportNextDraw; - mOldTargetRect.set(0, 0, 0, 0); - pingRenderLocked(true /* drawImmediate */); - } - } - - /** - * Redraws the background, the caption and the system inset backgrounds if something changed. - * - * @param newBounds The window bounds which needs to be drawn. - * @param fullscreen Whether the window is currently drawing in fullscreen. - */ - private void redrawLocked(Rect newBounds, boolean fullscreen) { - - // Make sure that the other thread has already prepared the render draw calls for the - // content. If any size is 0, we have to wait for it to be drawn first. - if (mLastContentWidth == 0 || mLastContentHeight == 0) { - return; - } - - // Content may not be drawn at the surface origin, so we want to keep the offset when we're - // resizing it. - final int left = mLastXOffset + newBounds.left; - final int top = mLastYOffset + newBounds.top; - final int width = newBounds.width(); - final int height = newBounds.height(); - - mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height); - - // Draw the caption and content backdrops in to our render node. - RecordingCanvas canvas = mFrameAndBackdropNode.beginRecording(width, height); - final Drawable drawable = mUserCaptionBackgroundDrawable != null - ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable; - - if (drawable != null) { - drawable.setBounds(0, 0, left + width, top); - drawable.draw(canvas); - } - - // The backdrop: clear everything with the background. Clipping is done elsewhere. - if (mResizingBackgroundDrawable != null) { - mResizingBackgroundDrawable.setBounds(0, 0, left + width, top + height); - mResizingBackgroundDrawable.draw(canvas); - } - mFrameAndBackdropNode.endRecording(); - - drawColorViews(left, top, width, height, fullscreen); - - // We need to render the node explicitly - mRenderer.drawRenderNode(mFrameAndBackdropNode); - - reportDrawIfNeeded(); - } - - private void drawColorViews(int left, int top, int width, int height, boolean fullscreen) { - if (mSystemBarBackgroundNode == null) { - return; - } - RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(width, height); - mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); - final int topInset = mSystemBarInsets.top; - if (mStatusBarColor != null) { - mStatusBarColor.setBounds(0, 0, left + width, topInset); - mStatusBarColor.draw(canvas); - } - - // We only want to draw the navigation bar if our window is currently fullscreen because we - // don't want the navigation bar background be moving around when resizing in docked mode. - // However, we need it for the transitions into/out of docked mode. - if (mNavigationBarColor != null && fullscreen) { - DecorView.getNavigationBarRect(width, height, mSystemBarInsets, mTmpRect, 1f); - mNavigationBarColor.setBounds(mTmpRect); - mNavigationBarColor.draw(canvas); - } - mSystemBarBackgroundNode.endRecording(); - mRenderer.drawRenderNode(mSystemBarBackgroundNode); - } - - /** Notify view root that a frame has been drawn by us, if it has requested so. */ - private void reportDrawIfNeeded() { - if (mReportNextDraw) { - if (mDecorView.isAttachedToWindow()) { - mDecorView.getViewRootImpl().reportDrawFinish(); - } - mReportNextDraw = false; - } - } - - /** - * Sends a message to the renderer to wake up and perform the next action which can be - * either the next rendering or the self destruction if mRenderer is null. - * Note: This call must be synchronized. - * - * @param drawImmediate if we should draw immediately instead of scheduling a frame - */ - private void pingRenderLocked(boolean drawImmediate) { - if (mChoreographer != null && !drawImmediate) { - mChoreographer.postFrameCallback(this); - } else { - doFrameUncheckedLocked(); - } - } - - void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) { - synchronized (this) { - mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; - } - } -} diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 74c2325d45a2..c14a6c1ca5ba 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -57,7 +57,6 @@ import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.RecordingCanvas; import android.graphics.Rect; -import android.graphics.Region; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; @@ -238,11 +237,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Rect mTempRect; private boolean mWindowResizeCallbacksAdded = false; - private Drawable.Callback mLastBackgroundDrawableCb = null; - private BackdropFrameRenderer mBackdropFrameRenderer = null; private Drawable mOriginalBackgroundDrawable; private Drawable mLastOriginalBackgroundDrawable; - private Drawable mResizingBackgroundDrawable; private BackgroundBlurDrawable mBackgroundBlurDrawable; private BackgroundBlurDrawable mLastBackgroundBlurDrawable; @@ -253,8 +249,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind */ @Nullable private Drawable mPendingWindowBackground; - private Drawable mCaptionBackgroundDrawable; - private Drawable mUserCaptionBackgroundDrawable; String mLogTag = TAG; private final Rect mFloatingInsets = new Rect(); @@ -329,26 +323,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } @Override - public boolean gatherTransparentRegion(Region region) { - boolean statusOpaque = gatherTransparentRegion(mStatusColorViewState, region); - boolean navOpaque = gatherTransparentRegion(mNavigationColorViewState, region); - boolean decorOpaque = super.gatherTransparentRegion(region); - - // combine bools after computation, so each method above always executes - return statusOpaque || navOpaque || decorOpaque; - } - - boolean gatherTransparentRegion(ColorViewState colorViewState, Region region) { - if (colorViewState.view != null && colorViewState.visible && isResizing()) { - // If a visible ColorViewState is in a resizing host DecorView, forcibly register its - // opaque area, since it's drawn by a different root RenderNode. It would otherwise be - // rejected by ViewGroup#gatherTransparentRegion() for the view not being VISIBLE. - return colorViewState.view.gatherTransparentRegion(region); - } - return false; // no opaque area added - } - - @Override public void onDraw(Canvas c) { super.onDraw(c); @@ -838,7 +812,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final MenuHelper helper; final boolean isPopup = !Float.isNaN(x) && !Float.isNaN(y); if (isPopup) { - helper = mWindow.mContextMenu.showPopup(getContext(), originalView, x, y); + helper = mWindow.mContextMenu.showPopup(originalView.getContext(), originalView, x, y); } else { helper = mWindow.mContextMenu.showDialog(originalView, originalView.getWindowToken()); } @@ -977,15 +951,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateColorViews(null /* insets */, false /* animate */); } if (drawable != null) { - mResizingBackgroundDrawable = enforceNonTranslucentBackground(drawable, - mWindow.isTranslucent() || mWindow.isShowingWallpaper()); - } else { - mResizingBackgroundDrawable = getResizingBackgroundDrawable( - mWindow.mBackgroundDrawable, mWindow.mBackgroundFallbackDrawable, - mWindow.isTranslucent() || mWindow.isShowingWallpaper()); - } - if (mResizingBackgroundDrawable != null) { - mResizingBackgroundDrawable.getPadding(mBackgroundPadding); + drawable.getPadding(mBackgroundPadding); + } else if (mWindow.mBackgroundDrawable != null) { + mWindow.mBackgroundDrawable.getPadding(mBackgroundPadding); + } else if (mWindow.mBackgroundFallbackDrawable != null) { + mWindow.mBackgroundFallbackDrawable.getPadding(mBackgroundPadding); } else { mBackgroundPadding.setEmpty(); } @@ -1451,7 +1421,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mWindow.getAttributes().flags, force); boolean show = state.attributes.isVisible(state.present, color, mWindow.getAttributes().flags, force); - boolean showView = show && !isResizing() && size > 0; + boolean showView = show && size > 0; boolean visibilityChanged = false; View view = state.view; @@ -1505,7 +1475,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } if (visibilityChanged) { view.animate().cancel(); - if (animate && !isResizing()) { + if (animate) { if (showView) { if (view.getVisibility() != VISIBLE) { view.setVisibility(VISIBLE); @@ -1834,10 +1804,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // Note that our ViewRootImpl object will not change. getViewRootImpl().addWindowCallbacks(this); mWindowResizeCallbacksAdded = true; - } else if (mBackdropFrameRenderer != null) { - // We are resizing and this call happened due to a configuration change. Tell the - // renderer about it. - mBackdropFrameRenderer.onConfigurationChange(); } updateBackgroundBlurRadius(); @@ -1877,8 +1843,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind st.menu.close(); } - releaseThreadedRenderer(); - if (mWindowResizeCallbacksAdded) { getViewRootImpl().removeWindowCallbacks(this); mWindowResizeCallbacksAdded = false; @@ -2158,14 +2122,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { - if (mBackdropFrameRenderer != null) { - loadBackgroundDrawablesIfNeeded(); - mBackdropFrameRenderer.onResourcesLoaded( - this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, - mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), - getCurrentColor(mNavigationColorViewState)); - } - final View root = inflater.inflate(layoutResource, null); // Put it below the color views. @@ -2174,63 +2130,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind initializeElevation(); } - private void loadBackgroundDrawablesIfNeeded() { - if (mResizingBackgroundDrawable == null) { - mResizingBackgroundDrawable = getResizingBackgroundDrawable(mWindow.mBackgroundDrawable, - mWindow.mBackgroundFallbackDrawable, mWindow.isTranslucent() - || mWindow.isShowingWallpaper()); - if (mResizingBackgroundDrawable == null) { - // We shouldn't really get here as the background fallback should be always - // available since it is defaulted by the system. - Log.w(mLogTag, "Failed to find background drawable for PhoneWindow=" + mWindow); - } - } - if (mCaptionBackgroundDrawable == null) { - mCaptionBackgroundDrawable = getContext().getDrawable( - R.drawable.decor_caption_title_focused); - } - if (mResizingBackgroundDrawable != null) { - mLastBackgroundDrawableCb = mResizingBackgroundDrawable.getCallback(); - mResizingBackgroundDrawable.setCallback(null); - } - } - - /** - * Returns the color used to fill areas the app has not rendered content to yet when the - * user is resizing the window of an activity in multi-window mode. - */ - public static Drawable getResizingBackgroundDrawable(@Nullable Drawable backgroundDrawable, - @Nullable Drawable fallbackDrawable, boolean windowTranslucent) { - if (backgroundDrawable != null) { - return enforceNonTranslucentBackground(backgroundDrawable, windowTranslucent); - } - - if (fallbackDrawable != null) { - return enforceNonTranslucentBackground(fallbackDrawable, windowTranslucent); - } - return new ColorDrawable(Color.BLACK); - } - - /** - * Enforces a drawable to be non-translucent to act as a background if needed, i.e. if the - * window is not translucent. - */ - private static Drawable enforceNonTranslucentBackground(Drawable drawable, - boolean windowTranslucent) { - if (!windowTranslucent && drawable instanceof ColorDrawable) { - ColorDrawable colorDrawable = (ColorDrawable) drawable; - int color = colorDrawable.getColor(); - if (Color.alpha(color) != 255) { - ColorDrawable copy = (ColorDrawable) colorDrawable.getConstantState().newDrawable() - .mutate(); - copy.setColor( - Color.argb(255, Color.red(color), Color.green(color), Color.blue(color))); - return copy; - } - } - return drawable; - } - void clearContentView() { for (int i = getChildCount() - 1; i >= 0; i--) { View v = getChildAt(i); @@ -2243,21 +2142,13 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @Override public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { - if (mBackdropFrameRenderer != null) { - mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets); - } - } + Rect stableInsets) {} @Override public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets) { if (mWindow.isDestroyed()) { // If the owner's window is gone, we should not be able to come here anymore. - releaseThreadedRenderer(); - return; - } - if (mBackdropFrameRenderer != null) { return; } getViewRootImpl().requestInvalidateRootRenderNode(); @@ -2265,28 +2156,23 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @Override public void onWindowDragResizeEnd() { - releaseThreadedRenderer(); updateColorViews(null /* insets */, false); getViewRootImpl().requestInvalidateRootRenderNode(); } @Override public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) { - if (mBackdropFrameRenderer == null) { - return false; - } - return mBackdropFrameRenderer.onContentDrawn(offsetX, offsetY, sizeX, sizeY); + return false; } @Override public void onRequestDraw(boolean reportNextDraw) { - if (mBackdropFrameRenderer != null) { - mBackdropFrameRenderer.onRequestDraw(reportNextDraw); - } else if (reportNextDraw) { - // If render thread is gone, just report immediately. - if (isAttachedToWindow()) { - getViewRootImpl().reportDrawFinish(); - } + if (!reportNextDraw) { + return; + } + // If render thread is gone, just report immediately. + if (isAttachedToWindow()) { + getViewRootImpl().reportDrawFinish(); } } @@ -2307,25 +2193,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLegacyNavigationBarBackgroundPaint); } - /** Release the renderer thread which is usually done when the user stops resizing. */ - private void releaseThreadedRenderer() { - if (mResizingBackgroundDrawable != null && mLastBackgroundDrawableCb != null) { - mResizingBackgroundDrawable.setCallback(mLastBackgroundDrawableCb); - mLastBackgroundDrawableCb = null; - } - - if (mBackdropFrameRenderer != null) { - mBackdropFrameRenderer.releaseRenderer(); - mBackdropFrameRenderer = null; - // Bring the shadow back. - updateElevation(); - } - } - - private boolean isResizing() { - return mBackdropFrameRenderer != null; - } - /** * The elevation gets set for the first time and the framework needs to be informed that * the surface layer gets created with the shadow size in mind. @@ -2348,7 +2215,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final boolean wasAdjustedForStack = mElevationAdjustedForStack; // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null) // since the shadow is bound to the content size and not the target size. - if ((windowingMode == WINDOWING_MODE_FREEFORM) && !isResizing()) { + if (windowingMode == WINDOWING_MODE_FREEFORM) { elevation = hasWindowFocus() ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP; // Add a maximum shadow height value to the top level view. @@ -2367,16 +2234,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // Don't change the elevation if we didn't previously adjust it for the stack it was in // or it didn't change. - if ((wasAdjustedForStack || mElevationAdjustedForStack) - && getElevation() != elevation) { - if (!isResizing()) { - mWindow.setElevation(elevation); - } else { - // Just suppress the shadow when resizing, don't adjust surface insets because it'll - // cause a flicker when drag resize for freeform window starts. #onContentDrawn() - // will compensate the offset when passing to BackdropFrameRenderer. - setElevation(elevation); - } + if ((wasAdjustedForStack || mElevationAdjustedForStack) && getElevation() != elevation) { + mWindow.setElevation(elevation); } } @@ -2390,16 +2249,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind getResources().getDisplayMetrics()); } - /** - * Provide an override of the caption background drawable. - */ - void setUserCaptionBackgroundDrawable(Drawable drawable) { - mUserCaptionBackgroundDrawable = drawable; - if (mBackdropFrameRenderer != null) { - mBackdropFrameRenderer.setUserCaptionBackgroundDrawable(drawable); - } - } - private static String getTitleSuffix(WindowManager.LayoutParams params) { if (params == null) { return ""; diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index a091e19e1985..2194c897ff0d 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -4055,7 +4055,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void setResizingCaptionDrawable(Drawable drawable) { - mDecor.setUserCaptionBackgroundDrawable(drawable); + // TODO(b/333724879): Deprecate this public API. The new caption in WM shell allows the app + // content to draw behind it directly if requested. } @Override diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java index ec6283922807..067e5e8813a7 100644 --- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -18,6 +18,9 @@ package com.android.internal.policy; import android.content.Context; import android.content.res.Resources; +import android.util.DisplayUtils; +import android.view.Display; +import android.view.DisplayInfo; import android.view.RoundedCorners; import com.android.internal.R; @@ -57,11 +60,31 @@ public class ScreenDecorationsUtils { bottomRadius = defaultRadius; } + // If the physical pixels are scaled, apply it here + float scale = getPhysicalPixelDisplaySizeRatio(context); + if (scale != 1f) { + topRadius = topRadius * scale; + bottomRadius = bottomRadius * scale; + } + // Always use the smallest radius to make sure the rounded corners will // completely cover the display. return Math.min(topRadius, bottomRadius); } + static float getPhysicalPixelDisplaySizeRatio(Context context) { + DisplayInfo displayInfo = new DisplayInfo(); + context.getDisplay().getDisplayInfo(displayInfo); + final Display.Mode maxDisplayMode = + DisplayUtils.getMaximumResolutionDisplayMode(displayInfo.supportedModes); + if (maxDisplayMode == null) { + return 1f; + } + return DisplayUtils.getPhysicalPixelDisplaySizeRatio( + maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(), + displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight()); + } + /** * If live rounded corners are supported on windows. */ diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 2f09a5550fd4..66b2a9c8a424 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -1299,6 +1299,21 @@ public class TransitionAnimation { == HardwareBuffer.USAGE_PROTECTED_CONTENT; } + /** + * Returns the luminance in 0~1. The surface control is the source of the hardware buffer, + * which will be used if the buffer is protected from reading. + */ + public static float getBorderLuma(@NonNull HardwareBuffer hwBuffer, + @NonNull ColorSpace colorSpace, @NonNull SurfaceControl sourceSurfaceControl) { + if (hasProtectedContent(hwBuffer)) { + // The buffer cannot be read. Capture another buffer which excludes protected content + // from the source surface. + return getBorderLuma(sourceSurfaceControl, hwBuffer.getWidth(), hwBuffer.getHeight()); + } + // Use the existing buffer directly. + return getBorderLuma(hwBuffer, colorSpace); + } + /** Returns the luminance in 0~1. */ public static float getBorderLuma(SurfaceControl surfaceControl, int w, int h) { final ScreenCapture.ScreenshotHardwareBuffer buffer = diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 37b72880dd0c..42fa6ac0407d 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -135,7 +135,7 @@ public class PerfettoProtoLogImpl implements IProtoLog { new DataSourceParams.Builder() .setBufferExhaustedPolicy( DataSourceParams - .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) .build(); mDataSource.register(params); this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; 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/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 0f4615a12ea2..58bddaecd3e7 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -59,7 +59,7 @@ public class NotificationRowIconView extends CachingIconView { @Override protected void onFinishInflate() { // If showing the app icon, we don't need background or padding. - if (Flags.notificationsUseAppIcon()) { + if (Flags.notificationsUseAppIcon() || Flags.notificationsUseAppIconInRow()) { setPadding(0, 0, 0, 0); setBackground(null); } 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_database_SQLiteRawStatement.cpp b/core/jni/android_database_SQLiteRawStatement.cpp index b6b788114d22..8fc13a82e74e 100644 --- a/core/jni/android_database_SQLiteRawStatement.cpp +++ b/core/jni/android_database_SQLiteRawStatement.cpp @@ -41,6 +41,11 @@ */ namespace android { +// A zero-length byte array that can be returned by getColumnBlob(). The theory is that +// zero-length blobs are common enough that it is worth having a single, global instance. The +// object is created in the jni registration function. It is never destroyed. +static jbyteArray emptyArray = nullptr; + // Helper functions. static sqlite3 *db(long statementPtr) { return sqlite3_db_handle(reinterpret_cast<sqlite3_stmt*>(statementPtr)); @@ -226,7 +231,7 @@ static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) { throwIfInvalidColumn(env, stmtPtr, col); const void* blob = sqlite3_column_blob(stmt(stmtPtr), col); if (blob == nullptr) { - return NULL; + return (sqlite3_column_type(stmt(stmtPtr), col) == SQLITE_NULL) ? NULL : emptyArray; } size_t size = sqlite3_column_bytes(stmt(stmtPtr), col); jbyteArray result = env->NewByteArray(size); @@ -316,8 +321,10 @@ static const JNINativeMethod sStatementMethods[] = int register_android_database_SQLiteRawStatement(JNIEnv *env) { - return RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteRawStatement", - sStatementMethods, NELEM(sStatementMethods)); + RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteRawStatement", + sStatementMethods, NELEM(sStatementMethods)); + emptyArray = MakeGlobalRefOrDie(env, env->NewByteArray(0)); + return 0; } } // namespace android diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index d48cdc4645c6..eaff7608ce3b 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -713,6 +713,19 @@ android_media_AudioSystem_getForceUse(JNIEnv *env, jobject thiz, jint usage) AudioSystem::getForceUse(static_cast<audio_policy_force_use_t>(usage))); } +static jint android_media_AudioSystem_setDeviceAbsoluteVolumeEnabled(JNIEnv *env, jobject thiz, + jint device, jstring address, + jboolean enabled, + jint stream) { + const char *c_address = env->GetStringUTFChars(address, nullptr); + int state = check_AudioSystem_Command( + AudioSystem::setDeviceAbsoluteVolumeEnabled(static_cast<audio_devices_t>(device), + c_address, enabled, + static_cast<audio_stream_type_t>(stream))); + env->ReleaseStringUTFChars(address, c_address); + return state; +} + static jint android_media_AudioSystem_initStreamVolume(JNIEnv *env, jobject thiz, jint stream, jint indexMin, jint indexMax) { @@ -3373,6 +3386,7 @@ static const JNINativeMethod gMethods[] = MAKE_AUDIO_SYSTEM_METHOD(setPhoneState), MAKE_AUDIO_SYSTEM_METHOD(setForceUse), MAKE_AUDIO_SYSTEM_METHOD(getForceUse), + MAKE_AUDIO_SYSTEM_METHOD(setDeviceAbsoluteVolumeEnabled), MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume), MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex), MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex), 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 1aa635c6ceb7..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, @@ -2201,6 +2201,10 @@ static jobject nativeGetStalledTransactionInfo(JNIEnv* env, jclass clazz, jint p return jStalledTransactionInfo; } +static void nativeNotifyShutdown() { + SurfaceComposerClient::notifyShutdown(); +} + // ---------------------------------------------------------------------------- SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env, @@ -2443,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 }, @@ -2476,6 +2480,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*) nativeGetStalledTransactionInfo }, {"nativeSetDesiredPresentTimeNanos", "(JJ)V", (void*) nativeSetDesiredPresentTimeNanos }, + {"nativeNotifyShutdown", "()V", + (void*)nativeNotifyShutdown }, // clang-format on }; diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp index 31f4e641b69e..d426f1240a7f 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.cpp +++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp @@ -88,7 +88,7 @@ bool punchHoles(const char *filePath, const uint64_t offset, ALOGD("Total number of LOAD segments %zu", programHeaders.size()); ALOGD("Size before punching holes st_blocks: %" PRIu64 - ", st_blksize: %ld, st_size: %" PRIu64 "", + ", st_blksize: %d, st_size: %" PRIu64 "", beforePunch.st_blocks, beforePunch.st_blksize, static_cast<uint64_t>(beforePunch.st_size)); } @@ -193,7 +193,7 @@ bool punchHoles(const char *filePath, const uint64_t offset, ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno); return false; } - ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64 + ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %d, st_size: %" PRIu64 "", afterPunch.st_blocks, afterPunch.st_blksize, static_cast<uint64_t>(afterPunch.st_size)); @@ -271,7 +271,7 @@ bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldL uint64_t blockSize = beforePunch.st_blksize; IF_ALOGD() { ALOGD("Extra field length: %hu, Size before punching holes st_blocks: %" PRIu64 - ", st_blksize: %ld, st_size: %" PRIu64 "", + ", st_blksize: %d, st_size: %" PRIu64 "", extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize, static_cast<uint64_t>(beforePunch.st_size)); } @@ -346,7 +346,7 @@ bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldL return false; } ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64 - ", st_blksize: %ld, st_size: %" PRIu64 "", + ", st_blksize: %d, st_size: %" PRIu64 "", afterPunch.st_blocks, afterPunch.st_blksize, static_cast<uint64_t>(afterPunch.st_size)); } diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto index 3abc462671a2..e560a944b94b 100644 --- a/core/proto/android/app/appexitinfo.proto +++ b/core/proto/android/app/appexitinfo.proto @@ -20,7 +20,7 @@ option java_multiple_files = true; package android.app; import "frameworks/base/core/proto/android/privacy.proto"; -import "frameworks/proto_logging/stats/enums/app/enums.proto"; +import "frameworks/proto_logging/stats/enums/app/app_enums.proto"; /** * An android.app.ApplicationExitInfo object. diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto index d9ed911515ba..c13753343ba8 100644 --- a/core/proto/android/app/appstartinfo.proto +++ b/core/proto/android/app/appstartinfo.proto @@ -20,7 +20,7 @@ option java_multiple_files = true; package android.app; import "frameworks/base/core/proto/android/privacy.proto"; -import "frameworks/proto_logging/stats/enums/app/enums.proto"; +import "frameworks/proto_logging/stats/enums/app/app_enums.proto"; /** * An android.app.ApplicationStartInfo object. diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto index 4c84944a7382..97f81484b84d 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -21,7 +21,7 @@ package android.os; import "frameworks/base/core/proto/android/os/powermanager.proto"; import "frameworks/base/core/proto/android/privacy.proto"; -import "frameworks/proto_logging/stats/enums/app/job/enums.proto"; +import "frameworks/proto_logging/stats/enums/app/job/job_enums.proto"; import "frameworks/proto_logging/stats/enums/telephony/enums.proto"; message BatteryStatsProto { diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index e3a438da5abc..921c41c8e52b 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -35,7 +35,7 @@ import "frameworks/base/core/proto/android/server/intentresolver.proto"; import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; import "frameworks/base/core/proto/android/util/common.proto"; import "frameworks/base/core/proto/android/privacy.proto"; -import "frameworks/proto_logging/stats/enums/app/enums.proto"; +import "frameworks/proto_logging/stats/enums/app/app_enums.proto"; option java_multiple_files = true; diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 00127c134ce6..a1e3dc1b9b4e 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -31,7 +31,7 @@ import "frameworks/base/core/proto/android/server/appstatetracker.proto"; import "frameworks/base/core/proto/android/server/statlogger.proto"; import "frameworks/base/core/proto/android/privacy.proto"; import "frameworks/base/core/proto/android/util/quotatracker.proto"; -import "frameworks/proto_logging/stats/enums/app/job/enums.proto"; +import "frameworks/proto_logging/stats/enums/app/job/job_enums.proto"; import "frameworks/proto_logging/stats/enums/server/job/enums.proto"; message JobSchedulerServiceDumpProto { diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index 2f865afd28c7..593bbc6f5d0d 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -26,7 +26,7 @@ import "frameworks/base/core/proto/android/os/worksource.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto"; import "frameworks/base/core/proto/android/privacy.proto"; -import "frameworks/proto_logging/stats/enums/app/enums.proto"; +import "frameworks/proto_logging/stats/enums/app/app_enums.proto"; import "frameworks/proto_logging/stats/enums/os/enums.proto"; import "frameworks/proto_logging/stats/enums/view/enums.proto"; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8541704ecfb9..6dbe44b483d2 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. --> @@ -4697,6 +4702,11 @@ <permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION" android:protectionLevel="signature" /> + <!-- Allows an application to use the RemoteKeyProvisioningService. + @hide --> + <permission android:name="android.permission.BIND_RKP_SERVICE" + android:protectionLevel="signature" /> + <!-- Allows an application to get enabled credential manager providers. @hide --> <permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" diff --git a/core/res/res/drawable/decor_caption_title.xml b/core/res/res/drawable/decor_caption_title.xml deleted file mode 100644 index 591605d33fae..000000000000 --- a/core/res/res/drawable/decor_caption_title.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_window_focused="true" - android:drawable="@drawable/decor_caption_title_focused" /> - <item android:drawable="@drawable/decor_caption_title_unfocused" /> -</selector> diff --git a/core/res/res/drawable/decor_caption_title_focused.xml b/core/res/res/drawable/decor_caption_title_focused.xml deleted file mode 100644 index 7d1c23052bdb..000000000000 --- a/core/res/res/drawable/decor_caption_title_focused.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> - -<shape android:shape="rectangle" - android:tintMode="multiply" - android:tint="#D8D8D8" - xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- Fading the primary color to 85% blackness --> - <solid android:color="?android:attr/colorPrimary" /> -</shape> diff --git a/core/res/res/drawable/decor_caption_title_unfocused.xml b/core/res/res/drawable/decor_caption_title_unfocused.xml deleted file mode 100644 index 2846d8ca6baa..000000000000 --- a/core/res/res/drawable/decor_caption_title_unfocused.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> - -<shape android:shape="rectangle" - android:tintMode="multiply" - android:tint="#F2F2F2" - xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- Fading the primary color to 95% blackness --> - <solid android:color="?android:attr/colorPrimary"/> -</shape> diff --git a/core/res/res/drawable/decor_close_button_dark.xml b/core/res/res/drawable/decor_close_button_dark.xml deleted file mode 100644 index 950e4fdaba0b..000000000000 --- a/core/res/res/drawable/decor_close_button_dark.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- -Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="32.0dp" - android:height="32.0dp" - android:viewportWidth="32.0" - android:viewportHeight="32.0" - android:tint="@color/decor_button_dark_color" - > - <group android:scaleX="0.5" - android:scaleY="0.5" - android:translateX="8.0" - android:translateY="8.0" > - <path - android:fillColor="@color/white" - android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/> - </group> -</vector> diff --git a/core/res/res/drawable/decor_close_button_light.xml b/core/res/res/drawable/decor_close_button_light.xml deleted file mode 100644 index d75cd25b23ee..000000000000 --- a/core/res/res/drawable/decor_close_button_light.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- -Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="32.0dp" - android:height="32.0dp" - android:viewportWidth="32.0" - android:viewportHeight="32.0" - android:tint="@color/decor_button_light_color" - > - <group android:scaleX="0.5" - android:scaleY="0.5" - android:translateX="8.0" - android:translateY="8.0" > - <path - android:fillColor="@color/white" - android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/> - </group> -</vector> diff --git a/core/res/res/drawable/decor_maximize_button_dark.xml b/core/res/res/drawable/decor_maximize_button_dark.xml deleted file mode 100644 index 619b460ce787..000000000000 --- a/core/res/res/drawable/decor_maximize_button_dark.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="32.0dp" - android:height="32.0dp" - android:viewportWidth="32.0" - android:viewportHeight="32.0" - android:tint="@color/decor_button_dark_color" - > - <group android:scaleX="0.5" - android:scaleY="0.5" - android:translateX="8.0" - android:translateY="8.0" > - <path - android:fillColor="@color/white" - android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> - <path - android:fillColor="@color/white" - android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/> - </group> -</vector> - - diff --git a/core/res/res/drawable/decor_maximize_button_light.xml b/core/res/res/drawable/decor_maximize_button_light.xml deleted file mode 100644 index 5b55fd20ee87..000000000000 --- a/core/res/res/drawable/decor_maximize_button_light.xml +++ /dev/null @@ -1,35 +0,0 @@ -<!-- -Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="32.0dp" - android:height="32.0dp" - android:viewportWidth="32.0" - android:viewportHeight="32.0" - android:tint="@color/decor_button_light_color" - > - <group android:scaleX="0.5" - android:scaleY="0.5" - android:translateX="8.0" - android:translateY="8.0" > - <path - android:fillColor="@color/white" - android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> - <path - android:fillColor="@color/white" - android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/> - </group> -</vector> diff --git a/core/res/res/layout/decor_caption.xml b/core/res/res/layout/decor_caption.xml deleted file mode 100644 index 02467369825d..000000000000 --- a/core/res/res/layout/decor_caption.xml +++ /dev/null @@ -1,52 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** Copyright 2015, 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. -*/ ---> - -<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:descendantFocusability="beforeDescendants" > - <LinearLayout - android:id="@+id/caption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="end" - android:background="@drawable/decor_caption_title" - android:focusable="false" - android:descendantFocusability="blocksDescendants" > - <Button - android:id="@+id/maximize_window" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/maximize_button_text" - android:background="@drawable/decor_maximize_button_dark" /> - <Button - android:id="@+id/close_window" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" - android:layout_gravity="center_vertical|end" - android:contentDescription="@string/close_button_text" - android:background="@drawable/decor_close_button_dark" /> - </LinearLayout> -</com.android.internal.widget.DecorCaptionView> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 7fe8641cd889..ca6a384bd96f 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Jou werkprofiel is nie meer op hierdie toestel beskikbaar nie"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Te veel wagwoordpogings"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrateur het toestel vir persoonlike gebruik afgestaan"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privaat ruimte is verwyder"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Jou organisasie laat nie privaat ruimtes op hierdie bestuurde toestel toe nie."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Toestel word bestuur"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Jou organisasie bestuur hierdie toestel en kan netwerkverkeer monitor. Tik vir besonderhede."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Programme kan toegang tot jou ligging kry"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Stembystand"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Snelsluit"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Antwoord"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Nuwe kennisgewing"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fisieke sleutelbord"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sekuriteit"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"D-paneel links"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"D-paneel regs"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"D-paneel middel"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> se onderskrifbalk."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> is in die BEPERK-groep geplaas"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"het \'n prent gestuur"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe dit werk"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Hangend …"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Stel Vingerafdrukslot weer op"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> het nie goed gewerk nie en is uitgevee"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> en <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> het nie goed gewerk nie en is uitgevee"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> het nie goed gewerk nie en is uitgevee. Stel dit weer op om jou foon met vingerafdruk te ontsluit."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> en <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> het nie goed gewerk nie en is uitgevee. Stel dit weer op om jou foon met jou vingerafdruk te ontsluit."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Stel Gesigslot weer op"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 1d1ded6ce468..218b13ae3de7 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"የሥራ መገለጫዎ ከዚህ በኋላ በዚህ መሣሪያ ላይ አይገኝም"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"በጣም ብዙ የይለፍ ቃል ሙከራዎች"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"አስተዳዳሪ መሣሪያዎን ለግል ጥቅም ትተውታል"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"የግል ቦታ ተወግዷል"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"ድርጅትዎ የግል ቦታዎችን በዚህ የሚተዳደር መሣሪያ ላይ አይፈቅድም።"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"መሣሪያው የሚተዳደር ነው"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"የእርስዎ ድርጅት ይህን መሣሪያ ያስተዳድራል፣ እና የአውታረ መረብ ትራፊክን ሊከታተል ይችላል። ዝርዝሮችን ለማግኘት መታ ያድርጉ።"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"መተግበሪያዎች የእርስዎን አካባቢ መድረስ ይችላሉ"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"የድምጽ እርዳታ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"መቆለፊያ"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"መልስ"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"አዲስ ማሳወቂያ"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"አካላዊ ቁልፍ ሰሌዳ"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ደህንነት"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"ከDpad በስተግራ"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"ከDpad በስተቀኝ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"የDpad ማዕከል"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"የ<xliff:g id="APP_NAME">%1$s</xliff:g> የሥዕል ገላጭ ጽሁፍ አሞሌ።"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ወደ የRESTRICTED ባልዲ ተከትቷል"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>፦"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"አንድ ምስል ልከዋል"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"እንዴት እንደሚሠራ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"በመጠባበቅ ላይ..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"በጣት አሻራ መክፈቻን እንደገና ያዋቅሩ"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> በደንብ እየሠራ አልነበረም እና ተሰርዟል"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> እና <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> በደንብ እየሠሩ አልነበረም እና ተሰርዘዋል"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> በደንብ እየሠራ አልነበረም እና ተሰርዟል። ስልክዎን በጣት አሻራ ለመክፈት እንደገና ያዋቅሩት።"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> እና <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> በደንብ እየሠሩ አልነበረም እና ተሰርዘዋል። ስልክዎን በጣት አሻራ ለመክፈት እንደገና ያዋቅሯቸው።"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"በመልክ መክፈትን እንደገና ያዋቅሩ"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 3a4a79ab6a29..d0e28b7b243a 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -205,6 +205,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"لم يعد ملفك الشخصي للعمل متاحًا على هذا الجهاز"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"تم إجراء محاولات كثيرة جدًا لإدخال كلمة المرور"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"تنازل المشرف عن الجهاز للاستخدام الشخصي"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"تمت إزالة المساحة الخاصّة"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"لا تسمح مؤسستك بالمساحات الخاصة على هذا الجهاز المُدار."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"تتم إدارة الجهاز"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"تدير مؤسستك هذا الجهاز ويمكنها مراقبة حركة بيانات الشبكة. يمكنك النقر للحصول على تفاصيل."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"يمكن للتطبيقات الوصول إلى موقعك الجغرافي"</string> @@ -2198,7 +2200,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"زرّ الاتجاه لليسار"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"زرّ الاتجاه لليمين"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"الزرّ المركزي"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"شريط الشرح لتطبيق <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"تم وضع <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> في الحزمة \"محظورة\"."</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"هذا المستخدم أرسل صورة"</string> @@ -2419,10 +2420,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"طريقة العمل"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"بانتظار الإزالة من الأرشيف…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"إعادة إعداد ميزة \"فتح الجهاز ببصمة الإصبع\""</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"هناك مشكلة في <xliff:g id="FINGERPRINT">%s</xliff:g> وتم حذفها"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"هناك مشكلة في <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> و<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> وتم حذفهما"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"هناك مشكلة في <xliff:g id="FINGERPRINT">%s</xliff:g> وتم حذفها. يُرجى إعدادها مرة أخرى لفتح قفل هاتفك باستخدام بصمة الإصبع."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"هناك مشكلة في <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> و<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> وتم حذفهما. يُرجى إعادة إعدادهما لفتح قفل هاتفك باستخدام بصمة الإصبع."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"إعادة إعداد ميزة \"فتح الجهاز بالتعرّف على الوجه\""</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index e8346b7115a5..42b0af9d4490 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইল এই ডিভাইচটোত আৰু উপলব্ধ নহয়"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"বহুতবাৰ ভুলকৈ পাছৱৰ্ড দিয়া হৈছে"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"প্ৰশাসকে ডিভাইচটো ব্যক্তিগত ব্যৱহাৰৰ বাবে বাজেয়প্ত কৰিছে"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"প্ৰাইভেট স্পে’চ আঁতৰোৱা হৈছে"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"আপোনাৰ প্ৰতিষ্ঠানে এই পৰিচালিত ডিভাইচত প্ৰাইভেট স্পে’চৰ অনুমতি নিদিয়ে।"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"পৰিচালিত ডিভাইচ"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"আপোনাৰ প্ৰতিষ্ঠানটোৱে এই ডিভাইচটো পৰিচালনা কৰে আৰু ই নেটৱৰ্কৰ ট্ৰেফিক পৰ্যবেক্ষণ কৰিব পাৰে। সবিশেষ জানিবলৈ টিপক।"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"এপ্সমূহে আপোনাৰ অৱস্থান এক্সেছ কৰিব পাৰে"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"ডিপেডৰ বাওঁফালৰ বুটাম"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"ডিপেডৰ সোঁফালৰ বুটাম"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"ডিপেডৰ মাজৰ বুটাম"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ কেপশ্বন বাৰ।"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>ক সীমাবদ্ধ বাকেটটোত ৰখা হৈছে"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"এখন প্ৰতিচ্ছবি পঠিয়াইছে"</string> @@ -2395,7 +2396,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"কীব’ৰ্ডৰ লে’আউট <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g> হিচাপে ছেট কৰা হৈছে… সলনি কৰিবলৈ টিপক।"</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"ভৌতিক কীব’ৰ্ড কনফিগাৰ কৰা হৈছে"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"কীব’ৰ্ড চাবলৈ টিপক"</string> - <string name="profile_label_private" msgid="6463418670715290696">"ব্যক্তিগত"</string> + <string name="profile_label_private" msgid="6463418670715290696">"প্ৰাইভেট"</string> <string name="profile_label_clone" msgid="769106052210954285">"ক্ল’ন"</string> <string name="profile_label_work" msgid="3495359133038584618">"কৰ্মস্থান"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"কৰ্মস্থান ২"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ই কেনেকৈ কাম কৰে"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"বিবেচনাধীন হৈ আছে..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ফিংগাৰপ্ৰিণ্ট আনলক পুনৰ ছেট আপ কৰক"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g>এ ভালদৰে কাম কৰা নাছিল আৰু সেইটো মচি পেলোৱা হৈছে"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> আৰু <xliff:g id="FINGERPRINT_1">%2$s</xliff:g>এ ভালদৰে কাম কৰা নাছিল আৰু সেয়া মচি পেলোৱা হৈছে"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g>এ ভালদৰে কাম কৰা নাছিল আৰু সেইটো মচি পেলোৱা হৈছে। ফিংগাৰপ্ৰিণ্টৰ জৰিয়তে আপোনাৰ ফ’নটো আনলক কৰিবলৈ এইটো পুনৰ ছেট আপ কৰক।"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> আৰু <xliff:g id="FINGERPRINT_1">%2$s</xliff:g>এ ভালদৰে কাম কৰা নাছিল আৰু সেয়া মচি পেলোৱা হৈছে। ফিংগাৰপ্ৰিণ্টৰ জৰিয়তে আপোনাৰ ফ’নটো আনলক কৰিবলৈ সেয়া পুনৰ ছেট আপ কৰক।"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ফে’চ আনলক পুনৰ ছেট আপ কৰক"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 5f9e9fa5443b..5633c06f6b92 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"İş profili artıq bu cihazda əlçatan deyil"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Həddindən çox parol cəhdi"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin şəxsi istifadə üçün cihazdan imtina etdi"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Şəxsi sahə silindi"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Təşkilat bu idarə olunan cihazda şəxsi sahələrə icazə vermir."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Cihaz idarə olunur"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Təşkilat bu cihazı idarə edir və şəbəkənin ötürülməsinə nəzarət edə bilər. Detallar üçün klikləyin."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Tətbiqlər məkanınıza daxil ola bilər"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Sola"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Sağa"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Mərkəzə"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> başlıq paneli."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> MƏHDUDLAŞDIRILMIŞ səbətinə yerləşdirilib"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"şəkil göndərdi"</string> @@ -2395,7 +2396,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Klaviatura düzəni <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g> kimi ayarlanıb… Dəyişmək üçün toxunun."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Fiziki klaviaturalar konfiqurasiya edilib"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Klaviaturalara baxmaq üçün toxunun"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Şəxsi"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Məxfi"</string> <string name="profile_label_clone" msgid="769106052210954285">"Klon"</string> <string name="profile_label_work" msgid="3495359133038584618">"İş"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"İş 2"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Haqqında"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Gözləmədə..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Barmaqla Kilidaçmanı yenidən ayarlayın"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> yaxşı işləmirdi və silindi"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> və <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> yaxşı işləmirdi və silindi"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> yaxşı işləmirdi və silindi. Telefonu barmaq izi ilə kiliddən çıxarmaq üçün onu yenidən ayarlayın."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> və <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> yaxşı işləmirdi və silindi. Telefonu barmaq izi ilə kiliddən çıxarmaq üçün onları yenidən ayarlayın."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Üzlə Kilidaçmanı yenidən ayarlayın"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index db9a93f7a63a..e38706ea866c 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Poslovni profil više nije dostupan na ovom uređaju"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Previše pokušaja unosa lozinke"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator je ustupio uređaj za ličnu upotrebu"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privatan prostor je uklonjen"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Organizacija ne dozvoljava privatne prostore na ovom upravljanom uređaju."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Uređajem se upravlja"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organizacija upravlja ovim uređajem i može da nadgleda mrežni saobraćaj. Dodirnite za detalje."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikacije mogu da pristupaju vašoj lokaciji"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Glasovna pomoć"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Odgovori"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Novo obaveštenje"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tastatura"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Bezbednost"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"nalevo na D-pad-u"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"nadesno na D-pad-u"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"centar na D-pad-u"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Traka sa naslovima aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> je dodat u segment OGRANIČENO"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"je poslao/la sliku"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Princip rada"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ponovo podesite otključavanje otiskom prsta"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> nije funkcionisao i izbrisali smo ga"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu funkcionisali i izbrisali smo ih"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nije funkcionisao i izbrisali smo ga. Ponovo ga podesite da biste telefon otključavali otiskom prsta."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu funkcionisali i izbrisali smo ih. Ponovo ih podesite da biste telefon otključavali otiskom prsta."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Ponovo podesite otključavanje licem"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index ec19c2da015e..25ae5a764360 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -203,6 +203,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Ваш працоўны профіль больш не даступны на гэтай прыладзе"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Занадта шмат спроб уводу пароля"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Адміністратар пераналадзіў прыладу для асабістага выкарыстання"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Прыватная прастора выдалена"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Ваша арганізацыя не дазваляе прыватныя прасторы на гэтай прыладзе пад яе кіраваннем."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Прылада знаходзіцца пад кіраваннем"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Ваша арганізацыя кіруе гэтай прыладай і можа сачыць за сеткавым трафікам. Дакраніцеся для атрымання дадатковай інфармацыі."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Праграмы могуць атрымліваць даныя пра ваша месцазнаходжанне"</string> @@ -285,8 +287,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Галас. дапамога"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Блакіроўка"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Адказаць"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Новае апавяшчэнне"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Фізічная клавіятура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Бяспека"</string> @@ -2197,7 +2198,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Улева на панэлі кіравання"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Управа на панэлі кіравання"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"У цэнтр на панэлі кіравання"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Панэль субцітраў праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакет \"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>\" дададзены ў АБМЕЖАВАНУЮ групу"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"адпраўлены відарыс"</string> @@ -2418,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як гэта працуе"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"У чаканні..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Наладзіць разблакіроўку адбіткам пальца паўторна"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Адбітак пальца \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" не працаваў належным чынам і быў выдалены"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Адбіткі пальцаў \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" і \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" не працавалі належным чынам і былі выдалены"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Адбітак пальца \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" не працаваў належным чынам і быў выдалены. Каб мець магчымасць разблакіраваць тэлефон з дапамогай адбітка пальца, наладзьце яго яшчэ раз."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Адбіткі пальцаў \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" і \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" не працавалі належным чынам і былі выдалены. Каб мець магчымасць разблакіраваць тэлефон з дапамогай адбітка пальца, наладзьце іх яшчэ раз."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Паўторна наладзьце распазнаванне твару"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 1b73710c4548..6b388d8d6e6d 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Служебният ви потребителски профил вече не е налице на това устройство"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Опитите за паролата са твърде много"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Администраторът предостави устройствотото за лична употреба"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Частното пространство бе премахнато"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Организацията ви не допуска частни пространства на това управлявано устройство."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Устройството се управлява"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Организацията ви управлява това устройство и може да наблюдава мрежовия трафик. Докоснете за подробности."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Прилож. имат достъп до местоположението ви"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Гласова помощ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Заключване"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Отговор"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Ново известие"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физическа клавиатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Сигурност"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Контролен пад – ляво"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Контролен пад – дясно"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Контролен пад – център"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Лента за надписи на <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакетът <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> е поставен в ОГРАНИЧЕНИЯ контейнер"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"изпратено изображение"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Начин на работа"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Изчаква..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Повторно настройване на „Отключване с отпечатък“"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Отпечатъкът „<xliff:g id="FINGERPRINT">%s</xliff:g>“ бе изтрит, защото не работеше добре"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Отпечатъците „<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>“ и „<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>“ бяха изтрити, защото не работеха добре"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Отпечатъкът „<xliff:g id="FINGERPRINT">%s</xliff:g>“ бе изтрит, защото не работеше добре. Настройте го отново, за да отключвате телефона си с отпечатък."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Отпечатъците „<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>“ и „<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>“ бяха изтрити, защото не работеха добре. Настройте ги отново, за да отключвате телефона си с отпечатък."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Повторно настройване на „Отключване с лице“"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 0e32bbe64c86..3f338cce08aa 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"আপনার কর্মস্থলের প্রোফাইলটি আর এই ডিভাইসে নেই"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"বহুবার ভুল পাসওয়ার্ড দিয়েছেন"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"ব্যক্তিগত কাজের জন্য অ্যাডমিন এই ডিভাইস ব্যবহার করার অনুমতি দেয়নি"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"প্রাইভেট স্পেস সরিয়ে দেওয়া হয়েছে"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"এই ম্যানেজ করা ডিভাইসে আপনার সংস্থা প্রাইভেট স্পেসের অনুমতি দেয় না।"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"ডিভাইসটি পরিচালনা করা হচ্ছে"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"আপনার প্রতিষ্ঠান এই ডিভাইসটি পরিচালনা করে এবং এটির নেটওয়ার্ক ট্রাফিকের উপরে নজর রাখতে পারে। বিশদ বিবরণের জন্য ট্যাপ করুন।,"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"অ্যাপগুলি আপনার লোকেশন অ্যাক্সেস করতে পারবে"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ভয়েস সহায়তা"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"লকডাউন"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"৯৯৯+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"উত্তর দিন"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"নতুন বিজ্ঞপ্তি"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ফিজিক্যাল কীবোর্ড"</string> <string name="notification_channel_security" msgid="8516754650348238057">"নিরাপত্তা"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"ডিপ্যাড (Dpad)-এর বাঁদিকে"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"ডিপ্যাড (Dpad)-এর ডানদিকে"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"ডিপ্যাড (Dpad)-এর মাঝখানে"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর ক্যাপশন বার।"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> সীমাবদ্ধ গ্রুপে অন্তর্ভুক্ত করা হয়েছে"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"একটি ছবি পাঠানো হয়েছে"</string> @@ -2396,7 +2396,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"কীবোর্ড লেআউট <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>-এ সেট করা আছে… পালটাতে ট্যাপ করুন।"</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"ফিজিক্যাল কীবোর্ড কনফিগার করা হয়েছে"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"কীবোর্ড দেখতে ট্যাপ করুন"</string> - <string name="profile_label_private" msgid="6463418670715290696">"ব্যক্তিগত"</string> + <string name="profile_label_private" msgid="6463418670715290696">"প্রাইভেট"</string> <string name="profile_label_clone" msgid="769106052210954285">"ক্লোন করুন"</string> <string name="profile_label_work" msgid="3495359133038584618">"অফিস"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"২য় অফিস"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"এটি কীভাবে কাজ করে"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"বাকি আছে…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"\'ফিঙ্গারপ্রিন্ট আনলক\' আবার সেট-আপ করুন"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ভালোভাবে কাজ করছিল না এবং সেটি মুছে ফেলা হয়েছে"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ও <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ভালোভাবে কাজ করছিল না এবং মুছে ফেলা হয়েছে"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ভালোভাবে কাজ করছিল না বলে সেটি মুছে ফেলা হয়েছে। ফিঙ্গারপ্রিন্ট ব্যবহার করে আপনার ফোন আনলক করতে হলে এটি আবার সেট-আপ করুন।"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ও <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ভালোভাবে কাজ করছিল না বলে মুছে ফেলা হয়েছে। ফিঙ্গারপ্রিন্ট ব্যবহার করে আপনার ফোন আনলক করতে হলে সেগুলি আবার সেট-আপ করুন।"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"\'ফেস আনলক\' আবার সেট-আপ করুন"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 63a83f04aaab..0438302d762b 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Radni profil više nije dostupan na ovom uređaju"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Previše puta ste pokušali otključati uređaj"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator je ustupio uređaj za ličnu upotrebu"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privatni prostor je uklonjen"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Organizacija ne dozvoljava privatne prostore na ovom uređaju kojim se upravlja."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Uređajem se upravlja."</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Vaša organizacija upravlja ovim uređajem i može pratiti mrežni saobraćaj. Dodirnite za detalje."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikacije mogu pristupiti vašoj lokaciji"</string> @@ -2195,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Upravljač lijevo"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Upravljač desno"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Upravljač sredina"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Traka za natpis aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> je stavljen u odjeljak OGRANIČENO"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"je poslao/la sliku"</string> @@ -2416,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako ovo funkcionira"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ponovo postavite otključavanje otiskom prsta"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Otisak prsta <xliff:g id="FINGERPRINT">%s</xliff:g> nije dobro funkcionirao, pa je izbrisan"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Otisci prstiju <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu dobro funkcionirali, pa su izbrisani"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Otisak prsta <xliff:g id="FINGERPRINT">%s</xliff:g> nije dobro funkcionirao, pa je izbrisan. Postavite ga ponovo da otključavate telefon otiskom prsta."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Otisci prstiju <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu dobro funkcionirali, pa su izbrisani. Postavite ih ponovo da otključavate telefon otiskom prsta."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Ponovo postavite otključavanje licem"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 1fd814d3561a..21f397b0e093 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"El teu perfil de treball ja no està disponible en aquest dispositiu"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Has intentat introduir la contrasenya massa vegades"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrador ha cedit el dispositiu per a ús personal"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"S\'ha suprimit l\'espai privat"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"La teva organització no permet espais privats en aquest dispositiu gestionat."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"El dispositiu està gestionat"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"La teva organització gestiona aquest dispositiu i és possible que supervisi el trànsit de xarxa. Toca per obtenir més informació."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Les aplicacions poden accedir a la teva ubicació"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Assist. per veu"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueig de seguretat"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"+999"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Respon"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificació nova"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclat físic"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Seguretat"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Creu direccional: esquerra"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Creu direccional: dreta"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Creu direccional: centre"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de títol de l\'aplicació <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> s\'ha transferit al segment RESTRINGIT"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ha enviat una imatge"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Com funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendent..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Torna a configurar Desbloqueig amb empremta digital"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> no funcionava correctament i s\'ha suprimit"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> no funcionaven correctament i s\'han suprimit"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> no funcionava correctament i s\'ha suprimit. Torna a configurar-la per desbloquejar el telèfon amb l\'empremta digital."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> no funcionaven correctament i s\'han suprimit. Torna a configurar-les per desbloquejar el telèfon amb l\'empremta digital."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Torna a configurar Desbloqueig facial"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index be4128f845a7..4013a034a5c3 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -203,6 +203,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Váš pracovní profil v tomto zařízení již není k dispozici"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Příliš mnoho pokusů o zadání hesla"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrátor zařízení uvolnil k osobnímu používání"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Soukromý prostor byl odstraněn"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Vaše organizace na tomto spravovaném zařízení soukromé prostory nepovoluje."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Zařízení je spravováno"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Toto zařízení je spravováno vaší organizací, která může sledovat síťový provoz. Podrobnosti zobrazíte klepnutím."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikace mají přístup k vaší poloze"</string> @@ -1636,7 +1638,7 @@ <string name="issued_by" msgid="7872459822431585684">"Vydal:"</string> <string name="validity_period" msgid="1717724283033175968">"Platnost:"</string> <string name="issued_on" msgid="5855489688152497307">"Datum vydání:"</string> - <string name="expires_on" msgid="1623640879705103121">"Platnost vyprší:"</string> + <string name="expires_on" msgid="1623640879705103121">"Platnost skončí:"</string> <string name="serial_number" msgid="3479576915806623429">"Sériové číslo:"</string> <string name="fingerprints" msgid="148690767172613723">"Digitální otisky:"</string> <string name="sha256_fingerprint" msgid="7103976380961964600">"Digitální otisk SHA-256"</string> @@ -2196,7 +2198,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad doleva"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad doprava"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad střed"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Popisek aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Balíček <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> byl vložen do sekce OMEZENO"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"posílá obrázek"</string> @@ -2397,9 +2398,9 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Rozložení klávesnice je nastaveno na <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Klepnutím jej změníte."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Fyzické klávesnice byly nakonfigurovány"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Klepnutím zobrazíte klávesnice"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Soukromé"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Soukromý"</string> <string name="profile_label_clone" msgid="769106052210954285">"Klonovat"</string> - <string name="profile_label_work" msgid="3495359133038584618">"Práce"</string> + <string name="profile_label_work" msgid="3495359133038584618">"Pracovní"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Práce 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Práce 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> @@ -2417,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to funguje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Čeká na vyřízení…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Opětovné nastavení odemknutí otiskem prstu"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> nefungoval správně a byl vymazán"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nefungovaly správně a byly vymazány"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nefungoval správně a byl vymazán. Pokud chcete telefon odemykat otiskem prstu, nastavte jej znovu."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nefungovaly správně a byly vymazány. Pokud chcete telefon odemykat otiskem prstu, nastavte je znovu."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Nastavte odemknutí obličejem znovu"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 9b2844b06f44..5226d562384c 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Din arbejdsprofil er ikke længere tilgængelig på denne enhed"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"For mange mislykkede adgangskodeforsøg"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratoren har gjort personlig brug af enheden utilgængelig"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Det private område er fjernet"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Din organisation tillader ikke private områder på denne administrerede enhed."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Dette er en administreret enhed"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Din organisation administrerer denne enhed og kan overvåge netværkstrafik. Tryk for at se info."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps kan få adgang til din lokation"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"D-pad, venstre"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"D-pad, højre"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"D-pad, midten"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Titellinje for <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> er blevet placeret i samlingen BEGRÆNSET"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sendte et billede"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Sådan fungerer det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Afventer…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfigurer fingeroplåsning igen"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> virkede ikke optimalt og er derfor slettet"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> virkede ikke optimalt og er derfor slettet"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> virkede ikke optimalt og er derfor slettet. Konfigurer den igen for at bruge fingeroplåsning på din telefon."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> virkede ikke optimalt og er derfor slettet. Konfigurer dem igen for at bruge dit fingeraftryk til at låse din telefon op."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfigurer ansigtsoplåsning igen"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 1df8954ef78d..949150ac18c4 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Dein Arbeitsprofil ist auf diesem Gerät nicht mehr verfügbar"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Zu viele falsche Passworteingaben"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator hat das Gerät zur persönlichen Nutzung abgegeben"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Vertrauliches Profil entfernt"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Deine Organisation erlaubt auf diesem verwalteten Gerät keine vertraulichen Profile."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Dies ist ein verwaltetes Gerät"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Deine Organisation verwaltet dieses Gerät und überprüft unter Umständen den Netzwerkverkehr. Tippe hier, um weitere Informationen zu erhalten."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps können auf deinen Standort zugreifen"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Sprachassistent"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Sperren"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Antworten"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Neue Benachrichtigung"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physische Tastatur"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sicherheit"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Steuerkreuz nach links"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Steuerkreuz nach rechts"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Steuerkreuz Mitte"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Untertitelleiste von <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> wurde in den BESCHRÄNKT-Bucket gelegt"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"hat ein Bild gesendet"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"So funktionierts"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ausstehend…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Entsperrung per Fingerabdruck neu einrichten"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> hat nicht einwandfrei funktioniert und wurde gelöscht"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> und <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> haben nicht einwandfrei funktioniert und wurden gelöscht"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> hat nicht einwandfrei funktioniert und wurde gelöscht. Richte ihn noch einmal ein, um dein Smartphone per Fingerabdruck zu entsperren."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> und <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> haben nicht einwandfrei funktioniert und wurden gelöscht. Richte sie noch einmal ein, um dein Smartphone per Fingerabdruck zu entsperren."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Entsperrung per Gesichtserkennung neu einrichten"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 3c7d16714849..11dac6c4e16b 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Το προφίλ εργασίας σας δεν είναι πια διαθέσιμο σε αυτήν τη συσκευή"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Πάρα πολλές προσπάθειες εισαγωγής κωδικού πρόσβασης"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Συσκευή από την οποία αποσύρθηκε ο διαχειριστής για προσωπική χρήση"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Ο ιδιωτικός χώρος καταργήθηκε"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Ο οργανισμός σας δεν επιτρέπει ιδιωτικούς χώρους σε αυτή τη διαχειριζόμενη συσκευή."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Η συσκευή είναι διαχειριζόμενη"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Ο οργανισμός σας διαχειρίζεται αυτήν τη συσκευή και ενδέχεται να παρακολουθεί την επισκεψιμότητα δικτύου. Πατήστε για λεπτομέρειες."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Οι εφαρμογές μπορούν να αποκτήσουν πρόσβαση στην τοποθεσία σας"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Φων.υποβοηθ."</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Κλείδωμα"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Απάντηση"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Νέα ειδοποίηση"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Κανονικό πληκτρολόγιο"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Ασφάλεια"</string> @@ -2195,14 +2196,13 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad αριστερά"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad δεξιά"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad κέντρο"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Γραμμή υποτίτλων για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Το πακέτο <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> τοποθετήθηκε στον κάδο ΠΕΡΙΟΡΙΣΜΕΝΗΣ ΠΡΟΣΒΑΣΗΣ."</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"έστειλε μια εικόνα"</string> <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Συνομιλία"</string> <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Ομαδική συνομιλία"</string> <string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string> - <string name="resolver_personal_tab" msgid="2051260504014442073">"Προσωπικό"</string> + <string name="resolver_personal_tab" msgid="2051260504014442073">"Προσωπικός"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"Εργασία"</string> <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Προσωπική προβολή"</string> <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Προβολή εργασίας"</string> @@ -2396,7 +2396,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Η διάταξη πληκτρολογίου ορίστηκε σε <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Πατήστε για αλλαγή."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Τα φυσικά πληκτρολόγια διαμορφώθηκαν"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Πατήστε για να δείτε πληκτρολόγια"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Ιδιωτικό"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Ιδιωτικός"</string> <string name="profile_label_clone" msgid="769106052210954285">"Κλώνος"</string> <string name="profile_label_work" msgid="3495359133038584618">"Εργασία"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Εργασία 2"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Πώς λειτουργεί"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Σε εκκρεμότητα…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Επαναρρύθμιση λειτουργίας Ξεκλείδωμα με δακτυλικό αποτύπωμα"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Το δακτυλικό αποτύπωμα <xliff:g id="FINGERPRINT">%s</xliff:g> δεν λειτουργούσε καλά και διαγράφηκε"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Τα δακτυλικά αποτυπώματα <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> και <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> δεν λειτουργούσαν καλά και διαγράφηκαν"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Το δακτυλικό αποτύπωμα <xliff:g id="FINGERPRINT">%s</xliff:g> δεν λειτουργούσε καλά και διαγράφηκε. Ρυθμίστε το ξανά για να ξεκλειδώνετε το τηλέφωνο με το δακτυλικό αποτύπωμά σας."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Τα δακτυλικά αποτυπώματα <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> και <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> δεν λειτουργούσαν καλά και διαγράφηκαν. Ρυθμίστε τα ξανά για να ξεκλειδώνετε το τηλέφωνο με το δακτυλικό αποτύπωμά σας."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Επαναρρύθμιση λειτουργίας Ξεκλείδωμα με το πρόσωπο"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 0c782a7f2d5e..1effe7c8f7d1 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Private space removed"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Your organisation does not allow private spaces on this managed device."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Your organisation manages this device and may monitor network traffic. Tap for details."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps can access your location"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad left"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad right"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad centre"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with your fingerprint."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index d1894b5e7106..4abc5816a744 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Private space removed"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Your organisation does not allow private spaces on this managed device."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Your organization manages this device and may monitor network traffic. Tap for details."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps can access your location"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Left"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Right"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Center"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 632585f7bc42..425d88ce49ea 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Private space removed"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Your organisation does not allow private spaces on this managed device."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Your organisation manages this device and may monitor network traffic. Tap for details."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps can access your location"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad left"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad right"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad centre"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with your fingerprint."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 1c5588450200..56ff14aa994c 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Private space removed"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Your organisation does not allow private spaces on this managed device."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Your organisation manages this device and may monitor network traffic. Tap for details."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps can access your location"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad left"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad right"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad centre"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with your fingerprint."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index e08f93466a0e..e599c0355c8b 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Private space removed"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Your organisation does not allow private spaces on this managed device."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Your organization manages this device and may monitor network traffic. Tap for details."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps can access your location"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Left"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Right"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Center"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 30a0457201b4..f5859bf78846 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Tu perfil de trabajo ya no está disponible en este dispositivo"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Demasiados intentos para ingresar la contraseña"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"El administrador no permite hacer un uso personal del dispositivo"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Se quitó el espacio privado"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Tu organización no permite espacios privados en este dispositivo administrado."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Dispositivo administrado"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Tu organización administra este dispositivo y es posible que controle el tráfico de red. Presiona para obtener más información."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Las apps pueden acceder a tu ubicación"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Asistente voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueo"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Responder"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nueva"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string> @@ -1190,8 +1191,8 @@ <string name="deleteText" msgid="4200807474529938112">"Eliminar"</string> <string name="inputMethod" msgid="1784759500516314751">"Método de entrada"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"Acciones de texto"</string> - <string name="error_handwriting_unsupported" msgid="7809438534946014050">"La función Escritura a mano no es compatible en este campo"</string> - <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"La función Escritura a mano no es compatible en los campos de contraseñas"</string> + <string name="error_handwriting_unsupported" msgid="7809438534946014050">"La función Escritura a mano no está disponible en este campo"</string> + <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"La Escritura a mano no está disponible en campos de contraseñas"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Atrás"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Cambiar método de entrada"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Queda poco espacio de almacenamiento"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Pad direccional: izquierda"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Pad direccional: derecha"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Pad direccional: centro"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de subtítulos de <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Se colocó <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> en el bucket RESTRICTED"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"envió una imagen"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendiente…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Vuelve a configurar el Desbloqueo con huellas dactilares"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Se borró <xliff:g id="FINGERPRINT">%s</xliff:g> porque no funcionaba correctamente"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Se borraron <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> y <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> porque no funcionaban correctamente"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Se borró <xliff:g id="FINGERPRINT">%s</xliff:g> porque no funcionaba correctamente. Vuelve a configurarla para desbloquear el teléfono con la huella dactilar."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Se borraron <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> y <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> porque no funcionaban correctamente. Vuelve a configurarlas para desbloquear el teléfono con la huella dactilar."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Vuelve a configurar el Desbloqueo facial"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 5079fdf415f9..d8b1f59a721d 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Tu perfil de trabajo ya no está disponible en este dispositivo"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Has fallado demasiadas veces al introducir la contraseña"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"El administrador no permite hacer un uso personal del dispositivo"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Espacio privado eliminado"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Tu organización no permite espacios privados en este dispositivo gestionado."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"El dispositivo está administrado"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Tu organización administra este dispositivo y puede supervisar el tráfico de red. Toca la notificación para obtener más información."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Las aplicaciones pueden acceder a tu ubicación"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Asistente voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueo de seguridad"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"> 999"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Responder"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nueva"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string> @@ -1190,8 +1191,8 @@ <string name="deleteText" msgid="4200807474529938112">"Eliminar"</string> <string name="inputMethod" msgid="1784759500516314751">"Método de introducción de texto"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"Acciones de texto"</string> - <string name="error_handwriting_unsupported" msgid="7809438534946014050">"Escritura a mano no está disponible en este campo"</string> - <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"Escritura a mano no está disponible en campos de contraseña"</string> + <string name="error_handwriting_unsupported" msgid="7809438534946014050">"La escritura a mano no está disponible en este campo"</string> + <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"La escritura a mano no está disponible en campos de contraseña"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Atrás"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Cambiar método de introducción de texto"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Queda poco espacio"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Cruceta: izquierda"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Cruceta: derecha"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Cruceta: centro"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de subtítulos de <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> se ha incluido en el grupo de restringidos"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ha enviado una imagen"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendiente..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configura Desbloqueo con huella digital de nuevo"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> no funcionaba correctamente y se ha eliminado"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> y <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> no funcionaban correctamente y se han eliminado"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> no funcionaba correctamente y se ha eliminado. Configúrala de nuevo para desbloquear el teléfono con la huella digital."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> y <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> no funcionaban correctamente y se han eliminado. Configúralas de nuevo para desbloquear el teléfono con la huella digital."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Configura Desbloqueo facial de nuevo"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 63e17564a865..77e665aa87a9 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Teie tööprofiil pole selles seadmes enam saadaval"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Liiga palju paroolikatseid"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administraator keelas seadme isikliku kasutamise"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privaatne ruum on eemaldatud"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Teie organisatsioon ei luba selles hallatud seadmes kasutada privaatseid ruume."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Seade on hallatud"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Teie organisatsioon haldab seda seadet ja võib jälgida võrguliiklust. Puudutage üksikasjade vaatamiseks."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Rakendused pääsevad teie asukohale juurde"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Suunaklahvistiku vasaknool"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Suunaklahvistiku paremnool"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Suunaklahvistiku keskmine nupp"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> pealkirjariba."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> on lisatud salve PIIRANGUTEGA"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"saatis kujutise"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Tööpõhimõtted"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ootel …"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Seadistage sõrmejäljega avamine uuesti"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ei töötanud hästi ja kustutati"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ja <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ei töötanud hästi ning kustutati"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ei töötanud hästi ja kustutati. Telefoni sõrmejäljega avamiseks seadistage see uuesti."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ja <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ei töötanud hästi ning kustutati. Telefoni sõrmejäljega avamiseks seadistage need uuesti."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Seadistage näoga avamine uuesti"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 4fb4726dce87..9bf2a4d0380e 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Laneko profila ez dago erabilgarri gailu honetan"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Gehiegitan saiatu zara pasahitza idazten"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Erabilera pertsonalerako utzi du gailua administratzaileak"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Kendu egin da eremu pribatua"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Zure erakundeak ez ditu onartzen eremu pribatuak kudeatutako gailu honetan."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Jabeak kudeatzen du gailua"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Erakundeak kudeatzen du gailua eta baliteke sareko trafikoa gainbegiratzea. Sakatu hau xehetasunak ikusteko."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikazioek zure kokapena atzi dezakete"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ahots-laguntza"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blokeatu"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Erantzun"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Jakinarazpen berria"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teklatu fisikoa"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Segurtasuna"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Norabide-kontrolagailuko ezkerreko botoia"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Norabide-kontrolagailuko eskuineko botoia"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Norabide-kontrolagailuko erdiko botoia"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioko azpitituluen barra."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Murriztuen edukiontzian ezarri da <xliff:g id="PACKAGE_NAME">%1$s</xliff:g>"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"erabiltzaileak irudi bat bidali du"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Nola funtzionatzen du?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Zain…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfiguratu berriro hatz-marka bidez desblokeatzeko eginbidea"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ezabatu egin da, ez zuelako ondo funtzionatzen"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> eta <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ezabatu egin dira, ez zutelako ondo funtzionatzen"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ezabatu egin da, ez zuelako ondo funtzionatzen. Telefonoa hatz-markarekin desblokeatzeko, konfigura ezazu berriro."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> eta <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ezabatu egin dira, ez zutelako ondo funtzionatzen. Telefonoa hatz-markarekin desblokeatzeko, konfigura itzazu berriro."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfiguratu berriro aurpegi bidez desblokeatzeko eginbidea"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 9a57d807b684..308260f0019d 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"نمایه کاری شما دیگر در این دستگاه دردسترس نیست"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"تلاشهای بسیار زیادی برای وارد کردن گذرواژه انجام شده است"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"سرپرست از این دستگاه برای استفاده شخصی چشمپوشی کرد"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"فضای خصوصی حذف شد"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"سازمان شما اجازه نمیدهد در این دستگاه مدیریتشده فضای خصوصی وجود داشته باشد."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"دستگاه مدیریت میشود"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"سازمانتان این دستگاه را مدیریت میکند و ممکن است ترافیک شبکه را پایش کند. برای اطلاع از جزئیات، ضربه بزنید."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"برنامهها میتوانند به مکانتان دسترسی پیدا کنند"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"دستیار صوتی"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"قفل همه"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"۹۹۹+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"پاسخ دادن"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"اعلان جدید"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"صفحهکلید فیزیکی"</string> <string name="notification_channel_security" msgid="8516754650348238057">"امنیت"</string> @@ -607,8 +608,8 @@ <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"به برنامه اجازه میدهد به دستگاههای بلوتوث مرتبطشده متصل شود"</string> <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"تبلیغ در دستگاههای بلوتوث اطراف"</string> <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"برنامه مجاز میشود در دستگاههای بلوتوث اطراف تبلیغ کند."</string> - <string name="permlab_uwb_ranging" msgid="8141915781475770665">"مشخص کردن موقعیت نسبی بین دستگاههای باند فوقوسیع اطراف"</string> - <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"به برنامه اجازه داده میشود موقعیت نسبی بین دستگاههای باند فوقوسیع اطراف را مشخص کند"</string> + <string name="permlab_uwb_ranging" msgid="8141915781475770665">"مشخص کردن موقعیت نسبی بین دستگاههای «فراپهنباند» اطراف"</string> + <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"به برنامه اجازه داده میشود موقعیت نسبی بین دستگاههای «فراپهنباند» اطراف را مشخص کند"</string> <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"برقراری تعامل با دستگاههای Wi-Fi اطراف"</string> <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"به برنامه اجازه میدهد در دستگاههای Wi-Fi اطراف تبلیغ کند، به آنها متصل شود، و موقعیت نسبی آنها را تشخیص دهد"</string> <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"اطلاعات ترجیحی سرویس پولی NFC"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"پد کنترل چپ"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"پد کنترل راست"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"پد کنترل وسط"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"نوار شرح <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> در سطل «محدودشده» قرار گرفت"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"تصویری ارسال کرد"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"روش کار"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"درحال تعلیق…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"راهاندازی مجدد «قفلگشایی با اثر انگشت»"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> خوب کار نمیکرد و حذف شد"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> و <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> خوب کار نمیکردند و حذف شدند"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> خوب کار نمیکرد و حذف شد. برای باز کردن قفل تلفن با اثر انگشت، آن را دوباره راهاندازی کنید."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> و <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> خوب کار نمیکرد و حذف شد. برای باز کردن قفل تلفن با اثر انگشت، آنها را دوباره راهاندازی کنید."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"راهاندازی مجدد «قفلگشایی با چهره»"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 7062a8955acb..db579f223781 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Työprofiilisi ei ole enää käytettävissä tällä laitteella."</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Liikaa salasanayrityksiä"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Järjestelmänvalvoja luovutti laitteen henkilökohtaiseen käyttöön"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Yksityinen tila poistettu"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Organisaatio ei salli yksityisiä tiloja tällä hallinnoidulla laitteella."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Hallinnoitu laite"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organisaatiosi hallinnoi tätä laitetta ja voi tarkkailla verkkoliikennettä. Katso lisätietoja napauttamalla."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Sovelluksilla on pääsy sijaintiisi"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ääniapuri"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lukitse"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Vastaa"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Uusi ilmoitus"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyysinen näppäimistö"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Turvallisuus"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Suuntanäppäimistö: vasen painike"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Suuntanäppäimistö: oikea painike"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Suuntanäppäimistö: keskipainike"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Tekstityspalkki: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> on nyt rajoitettujen ryhmässä"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"lähetti kuvan"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Näin se toimii"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Odottaa…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ota sormenjälkiavaus uudelleen käyttöön"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ei toiminut kunnolla, ja se poistettiin"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ja <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> eivät toimineet kunnolla, ja ne poistettiin"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ei toiminut kunnolla, ja se poistettiin. Ota se uudelleen käyttöön, jotta voit avata puhelimen lukituksen sormenjäljellä."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ja <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> eivät toimineet kunnolla, ja ne poistettiin. Ota ne uudelleen käyttöön, jotta voit avata puhelimen lukituksen sormenjäljellä."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Ota kasvojentunnistusavaus uudelleen käyttöön"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 9aa982fd468b..8888005be1d3 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Votre profil professionnel n\'est plus accessible sur cet appareil"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Trop de tentatives d\'entrée du mot de passe"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrateur a libéré l\'appareil pour un usage personnel"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Espace privé retiré"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Votre organisation n\'autorise pas les espaces privés sur cet appareil géré."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"L\'appareil est géré"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Votre organisation gère cet appareil et peut surveiller le trafic réseau. Touchez ici pour obtenir plus d\'information."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Les applications peuvent accéder à votre position"</string> @@ -2195,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Pavé directionnel – gauche"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Pavé directionnel – droite"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Pavé directionnel – centre"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barre de légende de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> a été placé dans le compartiment RESTREINT"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g> :"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"a envoyé une image"</string> @@ -2416,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"En attente…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configurer le Déverrouillage par empreinte digitale à nouveau"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ne fonctionnait pas bien et a été supprimée"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> et <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ne fonctionnaient pas bien et ont été supprimées"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ne fonctionnait pas bien et a été supprimée. Configurez-le à nouveau pour déverrouiller votre téléphone avec l\'empreinte digitale."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> et <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ne fonctionnaient pas bien et ont été supprimées. Configurez-les à nouveau pour déverrouiller votre téléphone avec votre empreinte digitale."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Configurer le Déverrouillage par reconnaissance faciale à nouveau"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index bb1b30d6d01d..4c2835d4f1cb 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -162,7 +162,7 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connecté au réseau chiffré <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connexion à la carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> est désormais plus sécurisée"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connecté à un réseau non chiffré"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Appels, messages et données sont plus vulnérables si vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion sera à nouveau chiffrée, vous recevrez une nouvelle notification."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Paramètres de sécurité du réseau mobile"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"En savoir plus"</string> @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Votre profil professionnel n\'est plus disponible sur cet appareil"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Trop de tentatives de saisie du mot de passe"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrateur a mis l\'appareil à disposition pour un usage personnel"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Espace privé supprimé"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Votre organisation n\'autorise pas les espaces privés sur cet appareil géré."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"L\'appareil est géré"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Votre organisation gère cet appareil et peut surveiller le trafic réseau. Appuyez ici pour obtenir plus d\'informations."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Des applications peuvent accéder à votre position"</string> @@ -2195,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Pavé directionnel - Gauche"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Pavé directionnel - Droite"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Pavé directionnel - Centre"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barre de légende de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> a été placé dans le bucket RESTRICTED"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g> :"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"a envoyé une image"</string> @@ -2416,14 +2417,12 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"En attente…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Reconfigurer le déverrouillage par empreinte digitale"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> - <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ne fonctionnait pas correctement et a été supprimée. Configurez-la à nouveau pour déverrouiller votre téléphone à l\'aide votre empreinte digitale."</string> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ne fonctionnait pas correctement et a été supprimée"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> et <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ne fonctionnaient pas correctement et ont été supprimées"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ne fonctionnait pas correctement et a été supprimée. Configurez-la à nouveau pour déverrouiller votre téléphone à l\'aide de votre empreinte digitale."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> et <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ne fonctionnaient pas correctement et ont été supprimées. Configurez-les à nouveau pour déverrouiller votre téléphone à l\'aide de votre empreinte digitale."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Reconfigurer le déverrouillage par reconnaissance faciale"</string> - <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Votre empreinte faciale ne fonctionnait pas correctement et a été supprimée. Configurez-la à nouveau pour déverrouiller votre téléphone à l\'aide votre visage."</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Votre empreinte faciale ne fonctionnait pas correctement et a été supprimée. Configurez-la à nouveau pour déverrouiller votre téléphone à l\'aide de votre visage."</string> <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configuration"</string> <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Pas maintenant"</string> </resources> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 1808743cd991..cb8e8046db81 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"O teu perfil de traballo xa non está dispoñible neste dispositivo"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Demasiados intentos de introdución do contrasinal"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador renunciou ao dispositivo para uso persoal"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Quitouse o espazo privado"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"A túa organización non permite espazos privados neste dispositivo xestionado."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"O dispositivo está xestionado"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"A túa organización xestiona este dispositivo e pode controlar o tráfico de rede. Toca para obter máis detalles."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"As aplicacións poden acceder á túa localización"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Cruceta: esquerda"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Cruceta: dereita"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Cruceta: centro"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de subtítulos de <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> incluíuse no grupo RESTRINXIDO"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviouse unha imaxe"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configura de novo o desbloqueo dactilar"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"A <xliff:g id="FINGERPRINT">%s</xliff:g> non funcionaba correctamente, polo que se eliminou"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"As impresións dixitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> non funcionaban correctamente, polo que se eliminaron"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A <xliff:g id="FINGERPRINT">%s</xliff:g> non funcionaba correctamente, polo que se eliminou. Configúraa de novo para desbloquear o teléfono usando a impresión dixital."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"As impresións dixitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> non funcionaban correctamente, polo que se eliminaron. Configúraas de novo para desbloquear o teléfono usando a impresión dixital."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Configura de novo o desbloqueo facial"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index f9b6ae3ae2e4..55032c9301af 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"આ ઉપકરણ પર તમારી કાર્યાલયની પ્રોફાઇલ હવે ઉપલબ્ધ નથી"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"પાસવર્ડના ઘણા વધુ પ્રયત્નો"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"વ્યવસ્થાપકે ડિવાઇસ વ્યક્તિગત ઉપયોગ માટે આપી દીધું છે"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"ખાનગી સ્પેસ કાઢી નાખી"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"મેનેજ કરેલા ડિવાઇસ પર, તમારી સંસ્થા દ્વારા ખાનગી સ્પેસને મંજૂરી આપવામાં આવતી નથી."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"ડિવાઇસ મેનેજ થયેલ છે"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"તમારી સંસ્થા આ ઉપકરણનું સંચાલન કરે છે અને નેટવર્ક ટ્રાફિફનું નિયમન કરી શકે છે. વિગતો માટે ટૅપ કરો."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ઍપ તમારા સ્થાનને ઍક્સેસ કરી શકે છે"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"ડી-પૅડ ડાબે"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"ડી-પૅડ જમણે"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"ડી-પૅડ મધ્યમાં"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>નું કૅપ્શન બાર."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>ને પ્રતિબંધિત સમૂહમાં મૂકવામાં આવ્યું છે"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"છબી મોકલી"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"તેની કામ કરવાની રીત"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"બાકી..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ફિંગરપ્રિન્ટ અનલૉક સુવિધાનું ફરી સેટઅપ કરો"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> યોગ્ય રીતે કામ કરતી ન હતી અને તેને ડિલીટ કરવામાં આવી હતી"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> અને <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> યોગ્ય રીતે કામ કરતી ન હતી અને તેને ડિલીટ કરવામાં આવી હતી"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> બરાબર કામ કરતી ન હતી અને તેને ડિલીટ કરવામાં આવી હતી. તમારા ફોનને ફિંગરપ્રિન્ટ વડે અનલૉક કરવા માટે, તેનું ફરીથી સેટઅપ કરો."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> અને <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> બરાબર કામ કરતી ન હતી અને તેને ડિલીટ કરવામાં આવી હતી. તમારા ફોનને તમારી ફિંગરપ્રિન્ટ વડે અનલૉક કરવા માટે, તેનું ફરીથી સેટઅપ કરો."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ફેસ અનલૉક સુવિધાનું ફરી સેટઅપ કરો"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index ae9b17c64235..e47715ab25f2 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"आपकी वर्क प्रोफ़ाइल अब इस डिवाइस पर उपलब्ध नहीं है"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"कई बार गलत पासवर्ड डाला गया"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"एडमिन ने निजी इस्तेमाल के लिए डिवाइस दे दिया है"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"प्राइवेट स्पेस हटाया गया"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"आपका संगठन मैनेज किए जा रहे इस डिवाइस पर प्राइवेट स्पेस रखने की अनुमति नहीं देता है."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"डिवाइस प्रबंधित है"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"आपका संगठन इस डिवाइस का प्रबंधन करता है और वह नेटवर्क ट्रैफ़िक की निगरानी भी कर सकता है. विवरण के लिए टैप करें."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ऐप्लिकेशन आपकी जगह की जानकारी ऐक्सेस कर सकते हैं"</string> @@ -1188,8 +1190,8 @@ <string name="deleteText" msgid="4200807474529938112">"मिटाएं"</string> <string name="inputMethod" msgid="1784759500516314751">"इनपुट विधि"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"लेख क्रियाएं"</string> - <string name="error_handwriting_unsupported" msgid="7809438534946014050">"इस फ़ील्ड में हैंडराइटिंग की सुविधा मौजूद नहीं है"</string> - <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"पासवर्ड वाले फ़ील्ड में हैंडराइटिंग की सुविधा मौजूद नहीं है"</string> + <string name="error_handwriting_unsupported" msgid="7809438534946014050">"हैंडराइटिंग की सुविधा, इस फ़ील्ड में काम नहीं करती है"</string> + <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"हैंडराइटिंग की सुविधा, पासवर्ड वाले फ़ील्ड में काम नहीं करती है"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"वापस जाएं"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"इनपुट का तरीका बदलें"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"मेमोरी में जगह नहीं बची है"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"डी-पैड का बाईं ओर वाला बटन"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"डी-पैड का दाईं ओर वाला बटन"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"डी-पैड का बीच वाला बटन"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> का कैप्शन बार."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> को प्रतिबंधित बकेट में रखा गया है"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"एक इमेज भेजी गई"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यह सेटिंग कैसे काम करती है"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"प्रोसेस जारी है..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"फ़िंगरप्रिंट अनलॉक की सुविधा दोबारा सेट अप करें"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"अच्छे से काम न करने की वजह से <xliff:g id="FINGERPRINT">%s</xliff:g> को मिटा दिया गया"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"अच्छे से काम न करने की वजह से, <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> और <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> को मिटा दिया गया"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"अच्छे से काम न करने की वजह से <xliff:g id="FINGERPRINT">%s</xliff:g> को मिटा दिया गया. फ़िंगरप्रिंट की मदद से फ़ोन अनलॉक करने के लिए, फ़िंगरप्रिंट अनलॉक की सुविधा को दोबारा सेट अप करें."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"अच्छे से काम न करने की वजह से, <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> और <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> को मिटा दिया गया. फ़िंगरप्रिंट की मदद से फ़ोन अनलॉक करने के लिए, फ़िंगरप्रिंट अनलॉक की सुविधा दोबारा सेट अप करें."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"फ़ेस अनलॉक की सुविधा को दोबारा सेट अप करें"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 0bd8be3b4472..0b9b662397f2 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Vaš poslovni profil više nije dostupan na ovom uređaju"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Previše pokušaja unosa zaporke"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator je ustupio uređaj za osobnu upotrebu"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privatni prostor je uklonjen"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Vaša organizacija ne dopušta privatne prostore na ovom upravljanom uređaju."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Uređaj je upravljan"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Vaša organizacija upravlja ovim uređajem i može nadzirati mrežni promet. Dodirnite za pojedinosti."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikacije mogu pristupiti vašoj lokaciji"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Glasovna pomoć"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zaključaj"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Odgovor"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Nova obavijest"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tipkovnica"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sigurnost"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Lijevo na plohi za smjerove"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Desno na plohi za smjerove"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"U središtu plohe za smjerove"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Traka naslova aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> premješten je u spremnik OGRANIČENO"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"šalje sliku"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako to funkcionira"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ponovno postavite otključavanje otiskom prsta"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Otisak prsta <xliff:g id="FINGERPRINT">%s</xliff:g> nije dobro funkcionirao i izbrisan je"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Otisci prstiju <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu dobro funkcionirali i izbrisani su"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Otisak prsta <xliff:g id="FINGERPRINT">%s</xliff:g> nije dobro funkcionirao i izbrisan je. Ponovno ga postavite da biste otključali telefon otiskom prsta."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Otisci prstiju <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu dobro funkcionirali i izbrisani su. Ponovno ih postavite da biste otključali telefon otiskom prsta."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Ponovno postavite otključavanje licem"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index aeebbdc22aac..de6777e8bce7 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Munkaprofilja már nem hozzáférhető ezen az eszközön."</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Túl sok jelszómegadási kísérlet"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Az adminisztrátor átadta az eszközt személyes használatra"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privát terület eltávolítva"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"A szervezete nem engedélyez privát területeket ezen a kezelt eszközön."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Felügyelt eszköz"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Ezt az eszközt szervezete kezeli, és lehetséges, hogy a hálózati forgalmat is figyelik. További részletekért koppintson."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Az alkalmazások hozzáférhetnek az Ön tartózkodási helyéhez"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Hangsegéd"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zárolás"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Válasz"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Új értesítés"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizikai billentyűzet"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Biztonság"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"D-pad – balra"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"D-pad – jobbra"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"D-pad – középre"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás címsora."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"A következő csomag a KORLÁTOZOTT csoportba került: <xliff:g id="PACKAGE_NAME">%1$s</xliff:g>"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"képet küldött"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hogyan működik?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Függőben…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"A Feloldás ujjlenyomattal funkció újbóli beállítása"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"A(z) <xliff:g id="FINGERPRINT">%s</xliff:g> nem működött megfelelően, ezért törölve lett"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"A(z) <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> és a(z) <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nem működtek megfelelően, ezért törölve lettek"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A(z) <xliff:g id="FINGERPRINT">%s</xliff:g> nem működött megfelelően, ezért törölve lett. Állítsa be újra, hogy feloldhassa a telefonját az ujjlenyomata segítségével."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"A(z) <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> és a(z) <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nem működtek megfelelően, ezért törölve lettek. Állítsa be őket újra, hogy feloldhassa a telefonját az ujjlenyomata segítségével."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Állítsa be újra az Arcalapú feloldást"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index a0e0bd218a18..58331c7a1b3b 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Ձեր աշխատանքային պրոֆիլն այս սարքում այլևս հասանելի չէ"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Գաղտնաբառը մուտքագրելու չափից շատ փորձեր են կատարվել"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Ադմինիստրատորը տրամադրել է սարքը անձնական օգտագործման համար"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Մասնավոր տարածքը հեռացվել է"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Ձեր կազմակերպությունն արգելում է մասնավոր տարածքներն այս կառավարվող սարքում։"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Սարքը կառավարվում է"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Ձեր կազմակերպությունը կառավարում է այս սարքը և կարող է վերահսկել ցանցի թրաֆիկը: Հպեք՝ մանրամասները դիտելու համար:"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Հավելվածներին հասանելի է ձեր տեղադրությունը"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ձայնային օգնութ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Արգելափակում"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Պատասխանել"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Նոր ծանուցում"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Ֆիզիկական ստեղնաշար"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Անվտանգություն"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad-ի «Ձախ» կոճակ"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad-ի «Աջ» կոճակ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad-ի «Կենտրոն» կոճակ"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի ենթագրերի գոտին։"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> փաթեթը գցվեց ՍԱՀՄԱՆԱՓԱԿՎԱԾ զամբյուղի մեջ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>՝"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"օգտատերը պատկեր է ուղարկել"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ինչպես է դա աշխատում"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Առկախ է…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Նորից կարգավորեք մատնահետքով ապակողպումը"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"«<xliff:g id="FINGERPRINT">%s</xliff:g>» մատնահետքը հեռացվել է, քանի որ լավ չէր աշխատում"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"«<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>» և «<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>» մատնահետքերը հեռացվել են, քանի որ լավ չէին աշխատում"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"«<xliff:g id="FINGERPRINT">%s</xliff:g>» մատնահետքը հեռացվել է, քանի որ լավ չէր աշխատում։ Նորից կարգավորեք այն՝ ձեր հեռախոսը մատնահետքով ապակողպելու համար։"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"«<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>» և «<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>» մատնահետքերը հեռացվել են, քանի որ լավ չէին աշխատում։ Նորից կարգավորեք դրանք՝ ձեր հեռախոսը մատնահետքով ապակողպելու համար։"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Նորից կարգավորեք դեմքով ապակողպումը"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index fb6180b0d126..eeaf2b6514fa 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profil kerja tidak tersedia lagi di perangkat ini"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Terlalu banyak kesalahan sandi"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin melepaskan perangkat untuk penggunaan pribadi"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Ruang privasi dihapus"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Organisasi Anda tidak mengizinkan adanya ruang privasi di perangkat terkelola ini."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Perangkat ini ada yang mengelola"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organisasi mengelola perangkat ini dan mungkin memantau traffic jaringan. Ketuk untuk melihat detailnya."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikasi dapat mengakses lokasi Anda"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Bantuan Suara"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Kunci total"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Balas"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Notifikasi baru"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Keyboard fisik"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Keamanan"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Kiri"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Kanan"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Tengah"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Kolom teks <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> telah dimasukkan ke dalam bucket DIBATASI"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"mengirim gambar"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara kerjanya"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Tertunda..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Siapkan Buka dengan Sidik Jari lagi"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> tidak berfungsi dengan baik dan telah dihapus"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> dan <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> tidak berfungsi dengan baik dan telah dihapus"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> tidak berfungsi dengan baik dan telah dihapus. Siapkan lagi untuk membuka kunci ponsel Anda dengan sidik jari."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> dan <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> tidak berfungsi dengan baik dan telah dihapus. Siapkan lagi untuk membuka kunci ponsel Anda dengan sidik jari."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Siapkan Buka dengan Wajah lagi"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 5b38461fc399..7e68a06cc9f8 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Vinnusniðið þitt er ekki lengur í boði á þessu tæki"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Of margar tilraunir til að slá inn aðgangsorð"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Kerfisstjóri lét af hendi tæki til einkanota"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Leynirými fjarlægt"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Fyrirtækið þitt leyfir ekki leynirými í þessu stýrða tæki."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Tækinu er stjórnað"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Fyrirtækið þitt stjórnar þessu tæki og kann að fylgjast með netnotkun. Ýttu hér til að fá upplýsingar."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Forrit hafa aðgang að staðsetningu þinni"</string> @@ -2194,14 +2196,13 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Vinstrihnappur stýriflatar"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Hægrihnappur stýriflatar"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Miðjuhnappur stýriflatar"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Skjátextastika <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> var sett í flokkinn TAKMARKAÐ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sendi mynd"</string> <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Samtal"</string> <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Hópsamtal"</string> <string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string> - <string name="resolver_personal_tab" msgid="2051260504014442073">"Persónulegt"</string> + <string name="resolver_personal_tab" msgid="2051260504014442073">"Einkasnið"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"Vinna"</string> <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Persónulegt yfirlit"</string> <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Vinnuyfirlit"</string> @@ -2395,7 +2396,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Lyklaskipan er stillt á <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Ýttu til að breyta."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Vélbúnaðarlyklaborð eru stillt"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Ýttu til að sjá lyklaborð"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Lokað"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Leynirými"</string> <string name="profile_label_clone" msgid="769106052210954285">"Afrit"</string> <string name="profile_label_work" msgid="3495359133038584618">"Vinna"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Vinna 2"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Svona virkar þetta"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Í bið…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Setja upp fingrafarskenni aftur"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> virkaði illa og var eytt."</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> virkuðu illa og var eytt."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> virkaði illa og var eytt. Settu það upp aftur til að taka símann úr lás með fingrafari."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> virkuðu illa og var eytt. Settu þau upp aftur til að taka símann úr lás með fingrafarinu þínu."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Setja upp andlitskenni aftur"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index f22b6531c66e..037e148cd60a 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -202,6 +202,10 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Il tuo profilo di lavoro non è più disponibile sul dispositivo"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Troppi tentativi di inserimento della password"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'amministratore ha abbandonato il dispositivo per uso personale"</string> + <!-- no translation found for private_space_deleted_by_admin (1484365588862066939) --> + <skip /> + <!-- no translation found for private_space_deleted_by_admin_details (7007781735201818689) --> + <skip /> <string name="network_logging_notification_title" msgid="554983187553845004">"Il dispositivo è gestito"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Questo dispositivo è gestito dalla tua organizzazione, che potrebbe monitorare il traffico di rete. Tocca per i dettagli."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Le app possono accedere alla tua posizione"</string> @@ -284,8 +288,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blocco"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Rispondi"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Nuova notifica"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastiera fisica"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sicurezza"</string> @@ -1903,7 +1906,7 @@ <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Richiedi il PIN per lo sblocco"</string> <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Richiedi sequenza di sblocco prima di sbloccare"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Richiedi password prima di sbloccare"</string> - <string name="package_installed_device_owner" msgid="8684974629306529138">"Installato dall\'amministratore.\nVai alle impostazioni per visualizzare le autorizzazioni"</string> + <string name="package_installed_device_owner" msgid="8684974629306529138">"Installato dall\'amministratore.\nVai alle impostazioni per visualizzare le autorizzazioni concesse"</string> <string name="package_updated_device_owner" msgid="7560272363805506941">"Aggiornato dall\'amministratore"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminato dall\'amministratore"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> @@ -2196,7 +2199,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"D-pad - Sinistra"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"D-pad - Destra"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"D-pad - Centro"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra del titolo di <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> è stato inserito nel bucket RESTRICTED"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ha inviato un\'immagine"</string> @@ -2417,10 +2419,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Come funziona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"In attesa…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Riconfigura lo Sblocco con l\'Impronta"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> non funzionava bene ed è stata eliminata"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> non funzionavano bene e sono state eliminate"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> non funzionava bene ed è stata eliminata. Riconfigurala per sbloccare lo smartphone con l\'impronta."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> non funzionavano bene e sono state eliminate. Riconfigurale per sbloccare lo smartphone con l\'impronta."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Riconfigura lo Sblocco con il Volto"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 3a924e1916c0..a1e72da42cae 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"פרופיל העבודה שלך אינו זמין עוד במכשיר הזה"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"בוצעו ניסיונות רבים מדי להזנת סיסמה"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"מנהל המערכת ביטל את האפשרות לשימוש במכשיר לצרכים אישיים"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"המרחב הפרטי הוסר"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"הארגון שלך לא מאפשר שימוש במרחבים פרטיים במכשיר המנוהל הזה."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"המכשיר מנוהל"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"הארגון שלך מנהל את המכשיר הזה והוא עשוי לנטר את התנועה ברשת. יש להקיש לקבלת פרטים."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"לאפליקציות יש הרשאת גישה למיקום שלך"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"האסיסטנט"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"נעילה"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"תשובה"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"התראה חדשה"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"מקלדת פיזית"</string> <string name="notification_channel_security" msgid="8516754650348238057">"אבטחה"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"לחצן שמאלי ב-Dpad"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"לחצן ימני ב-Dpad"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"לחצן אמצעי ב-Dpad"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"סרגל כיתוב של <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> התווספה לקטגוריה \'מוגבל\'"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"נשלחה תמונה"</string> @@ -2397,7 +2397,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"פריסת המקלדת מוגדרת ל<xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… אפשר להקיש כדי לשנות את ההגדרה הזו."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"הוגדרו מקלדות פיזיות"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"יש להקיש כדי להציג את המקלדות"</string> - <string name="profile_label_private" msgid="6463418670715290696">"פרופיל פרטי"</string> + <string name="profile_label_private" msgid="6463418670715290696">"פרטי"</string> <string name="profile_label_clone" msgid="769106052210954285">"שכפול"</string> <string name="profile_label_work" msgid="3495359133038584618">"פרופיל עבודה"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"פרופיל עבודה 2"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"איך זה עובד"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"בהמתנה..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"הגדרה חוזרת של \'ביטול הנעילה בטביעת אצבע\'"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> לא פעלה טוב ולכן היא נמחקה"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ו<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> לא פעלו טוב ולכן הן נמחקו"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> לא פעלה היטב ולכן היא נמחקה. עליך להגדיר אותה שוב כדי שתהיה לך אפשרות לבטל את הנעילה של הטלפון באמצעות טביעת אצבע."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ו<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> לא פעלו היטב ולכן הן נמחקו. עליך להגדיר אותן שוב כדי שתהיה לך אפשרות לבטל את הנעילה של הטלפון באמצעות טביעת אצבע."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"הגדרה חוזרת של \'פתיחה ע\"י זיהוי הפנים\'"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index a802d9027599..04e47a782e12 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"お使いの仕事用プロファイルはこのデバイスで使用できなくなりました"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"パスワード入力回数が上限に達しました"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"管理者により、デバイスの個人使用が許可されました"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"プライベート スペースの削除"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"この管理対象デバイス上のプライベート スペースは組織で許可されていません。"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"管理対象のデバイス"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"このデバイスは組織によって管理され、ネットワーク トラフィックが監視される場合があります。詳しくはタップしてください。"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"アプリに位置情報へのアクセスを許可しました"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"D-pad: 左"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"D-pad: 右"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"D-pad: 中央"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> のキャプション バーです。"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> は RESTRICTED バケットに移動しました。"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"画像を送信しました"</string> @@ -2395,7 +2396,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"キーボードのレイアウトは<xliff:g id="LAYOUT_1">%1$s</xliff:g>、<xliff:g id="LAYOUT_2">%2$s</xliff:g>、<xliff:g id="LAYOUT_3">%3$s</xliff:g>などに設定されています。タップで変更できます。"</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"物理キーボードの設定完了"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"タップするとキーボードを表示できます"</string> - <string name="profile_label_private" msgid="6463418670715290696">"非公開"</string> + <string name="profile_label_private" msgid="6463418670715290696">"プライベート"</string> <string name="profile_label_clone" msgid="769106052210954285">"複製"</string> <string name="profile_label_work" msgid="3495359133038584618">"仕事用"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"仕事用 2"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"仕組み"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"保留中..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"指紋認証をもう一度設定してください"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> は正常に機能せず、削除されました"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> と <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> は正常に機能せず、削除されました"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> が正常に機能せず、削除されました。指紋認証でスマートフォンのロックを解除するには、設定し直してください。"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> と <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> が正常に機能せず、削除されました。指紋認証でスマートフォンのロックを解除するには、設定し直してください。"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"顔認証をもう一度設定してください"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 338ec1cbe9a8..4f87855446d6 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"თქვენი სამსახურის პროფილი აღარ არის ხელმისაწვდომი ამ მოწყობილობაზე"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"დაფიქსირდა პაროლის შეყვანის ზედმეტად ბევრი მცდელობა"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"ადმინისტრატორმა გაათავისუფლა მოწყობილობა პირადი გამოყენებისთვის"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"კერძო სივრცე ამოშლილია"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"თქვენი ორგანიზაცია არ უშვებს ამ მართულ მოწყობილობაზე პირად სივრცეებს."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"მოწყობილობა მართულია"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"ამ მოწყობილობას თქვენი ორგანიზაცია მართავს და მას ქსელის ტრაფიკის მონიტორინგი შეუძლია. შეეხეთ დამატებითი დეტალებისთვის."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"აპებს შეუძლია თქვენს მდებარეობაზე წვდომა"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad მარცხნივ"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad მარჯვნივ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad ცენტრი"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის სუბტიტრების ზოლი."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> მოთავსდა კალათაში „შეზღუდული“"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"გაიგზავნა სურათი"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"მუშაობის პრინციპი"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"მომლოდინე..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ანაბეჭდით განბლოკვის ხელახლა დაყენება"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> კარგად არ მუშაობდა და წაიშალა"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> და <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> კარგად არ მუშაობდნენ და წაიშალა"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> კარგად არ მუშაობდა და წაიშალა. თავიდან დააყენეთ, რათა თქვენი ტელეფონი თითის ანაბეჭდის საშუალებით განბლოკოთ."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> და <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> კარგად არ მუშაობდნენ და წაიშალა. თავიდან დააყენეთ, რათა თქვენი ტელეფონი თითის ანაბეჭდის საშუალებით განბლოკოთ."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"დააყენეთ სახით განბლოკვა ხელახლა"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index e407c09b5a4e..f36d9fca2a35 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Жұмыс профиліңіз осы құрылғыда енді қолжетімді емес"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Құпия сөз көп рет қате енгізілді"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Әкімші құрылғыны жеке пайдалануға ұсынды."</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Құпия кеңістік өшірілді"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Ұйымыңыз басқаратын құрылғыда құпия кеңістік мүмкіндіктерін пайдалануға рұқсат етілмейді."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Құрылғы басқарылады"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Ұйымыңыз осы құрылғыны басқарады және желі трафигін бақылауы мүмкін. Мәліметтер алу үшін түртіңіз."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Қолданбалар геодерегіңізді пайдалана алады"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Дауыс көмекшісі"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Құлыптау"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Жауап беру"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Жаңа хабарландыру"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физикалық пернетақта"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Қауіпсіздік"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Сол жақ Dpad түймесі"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Оң жақ Dpad түймесі"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Ортаңғы Dpad түймесі"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасының жазу жолағы."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ШЕКТЕЛГЕН себетке салынды."</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"сурет жіберілді"</string> @@ -2396,7 +2396,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Пернетақта схемасы \"<xliff:g id="LAYOUT_1">%1$s</xliff:g>\", \"<xliff:g id="LAYOUT_2">%2$s</xliff:g>\", \"<xliff:g id="LAYOUT_3">%3$s</xliff:g>\" деп орнатылды… Өзгерту үшін түртіңіз."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Физикалық пернетақталар конфигурацияланды"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Пернетақталарды көру үшін түртіңіз."</string> - <string name="profile_label_private" msgid="6463418670715290696">"Жеке"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Құпия"</string> <string name="profile_label_clone" msgid="769106052210954285">"Клон"</string> <string name="profile_label_work" msgid="3495359133038584618">"Жұмыс"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Жұмыс 2"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Бұл қалай орындалады?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Дайын емес…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Саусақ ізімен ашу функциясын қайта реттеу"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> саусақ ізі дұрыс істемегендіктен жойылды."</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Саусақ іздері (<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> және <xliff:g id="FINGERPRINT_1">%2$s</xliff:g>) дұрыс істемегендіктен жойылды."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> саусақ ізі дұрыс істемегендіктен жойылды. Телефонды саусақ ізімен ашу үшін оны қайта реттеңіз."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Саусақ іздері (<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> және <xliff:g id="FINGERPRINT_1">%2$s</xliff:g>) дұрыс істемегендіктен жойылды. Телефонды саусақ ізімен ашу үшін оларды қайта реттеңіз."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Бет тану функциясын қайта реттеу"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index b0210384ae35..195eb133c872 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"កម្រងព័ត៌មានការងាររបស់អ្នកលែងមាននៅលើឧបករណ៍នេះទៀតហើយ"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ការព្យាយាមបញ្ចូលពាក្យសម្ងាត់ច្រើនដងពេកហើយ"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"អ្នកគ្រប់គ្រងបានបោះបង់ឧបករណ៍ចោលដោយសារការប្រើប្រាស់ផ្ទាល់ខ្លួន"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"លំហឯកជនត្រូវបានដកចេញ"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"ស្ថាប័នរបស់អ្នកមិនអនុញ្ញាតលំហឯកជននៅលើឧបករណ៍ដែលស្ថិតក្រោមការគ្រប់គ្រងនេះទេ។"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"ឧបករណ៍ស្ថិតក្រោមការគ្រប់គ្រង"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"ស្ថាប័នរបស់អ្នកគ្រប់គ្រងឧបករណ៍នេះ ហើយអាចនឹងតាមដានចរាចរណ៍បណ្តាញ។ ចុចដើម្បីទទួលបានព័ត៌មានលម្អិត។"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"កម្មវិធីអាចចូលប្រើទីតាំងរបស់អ្នកបាន"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ឆ្វេង"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad ស្ដាំ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad កណ្ដាល"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"របារពណ៌នាអំពី <xliff:g id="APP_NAME">%1$s</xliff:g>។"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ត្រូវបានដាក់ទៅក្នុងធុងដែលបានដាក់កំហិត"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>៖"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"បានផ្ញើរូបភាព"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"របៀបដែលវាដំណើរការ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"កំពុងរង់ចាំ..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"រៀបចំការដោះសោដោយស្កេនស្នាមម្រាមដៃម្ដងទៀត"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> មិនដំណើរការល្អទេ ហើយត្រូវបានលុបចេញហើយ"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> និង <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> មិនដំណើរការល្អទេ ហើយត្រូវបានលុបចេញហើយ"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> មិនដំណើរការល្អទេ ហើយត្រូវបានលុបចេញហើយ។ រៀបចំវាម្ដងទៀត ដើម្បីដោះសោទូរសព្ទរបស់អ្នកដោយប្រើស្នាមម្រាមដៃ។"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> និង <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> មិនដំណើរការល្អទេ ហើយត្រូវបានលុបចេញហើយ។ រៀបចំវាម្ដងទៀត ដើម្បីដោះសោទូរសព្ទរបស់អ្នកដោយប្រើស្នាមម្រាមដៃ។"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"រៀបចំការដោះសោដោយស្កេនមុខម្ដងទៀត"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 535c12566acd..073e44885f6c 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ಈ ಸಾಧನದಲ್ಲಿ ಈಗ ಲಭ್ಯವಿಲ್ಲ"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ಹಲವಾರು ಪಾಸ್ವರ್ಡ್ ಪ್ರಯತ್ನಗಳು"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"ವೈಯಕ್ತಿಕ ಬಳಕೆಗಾಗಿ ನಿರ್ವಾಹಕರು ತೊರೆದ ಸಾಧನ"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"ಈ ನಿರ್ವಹಿಸಲಾದ ಸಾಧನದಲ್ಲಿ ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್ಗಳನ್ನು ನಿಮ್ಮ ಸಂಸ್ಥೆಯು ಅನುಮತಿಸುವುದಿಲ್ಲ."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"ಸಾಧನವನ್ನು ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"ನಿಮ್ಮ ಸಂಸ್ಥೆಯು ಈ ಸಾಧನವನ್ನು ನಿರ್ವಹಿಸುತ್ತದೆ ಮತ್ತು ಅದು ನೆಟ್ವರ್ಕ್ ಟ್ರಾಫಿಕ್ ಮೇಲೆ ಗಮನವಿರಿಸಬಹುದು. ವಿವರಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ಆ್ಯಪ್ಗಳು ನಿಮ್ಮ ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಬಹುದು"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ನ ಎಡಭಾಗದ ಬಟನ್"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad ನ ಬಲಭಾಗದ ಬಟನ್"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad ನ ಮಧ್ಯದ ಬಟನ್"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್ನ ಶೀರ್ಷಿಕೆಯ ಪಟ್ಟಿ."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ಬಂಧಿತ ಬಕೆಟ್ಗೆ ಹಾಕಲಾಗಿದೆ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ಚಿತ್ರವನ್ನು ಕಳುಹಿಸಲಾಗಿದೆ"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ಇದು ಹೇಗೆ ಕೆಲಸ ಮಾಡುತ್ತದೆ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ಬಾಕಿ ಉಳಿದಿದೆ..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಅನ್ಲಾಕ್ ಅನ್ನು ಮತ್ತೊಮ್ಮೆ ಸೆಟಪ್ ಮಾಡಿ"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿರಲಿಲ್ಲ, ಹಾಗಾಗಿ ಅದನ್ನು ಅಳಿಸಲಾಗಿದೆ"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ಮತ್ತು <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿರಲಿಲ್ಲ, ಹಾಗಾಗಿ ಅವುಗಳನ್ನು ಅಳಿಸಲಾಗಿದೆ"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿಲ್ಲ ಹಾಗೂ ಅದನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಫಿಂಗರ್ ಪ್ರಿಂಟ್ ಮೂಲಕ ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಲು ಅದನ್ನು ಪುನಃ ಸೆಟಪ್ ಮಾಡಿ."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ಮತ್ತು <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿಲ್ಲ ಹಾಗೂ ಅವುಗಳನ್ನು ಅಳಿಸಲಾಗಿದೆ. ನಿಮ್ಮ ಫಿಂಗರ್ ಪ್ರಿಂಟ್ ಮೂಲಕ ನಿಮ್ಮ ಫೋನ್ ಅನ್ಲಾಕ್ ಮಾಡಲು ಅವುಗಳನ್ನು ಪುನಃ ಸೆಟಪ್ ಮಾಡಿ."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ಫೇಸ್ ಅನ್ಲಾಕ್ ಅನ್ನು ಮತ್ತೊಮ್ಮೆ ಸೆಟಪ್ ಮಾಡಿ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 7a8d324f0950..bbe8aef4bc70 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"직장 프로필을 이 기기에서 더 이상 사용할 수 없습니다."</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"비밀번호 입력을 너무 많이 시도함"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"관리자가 기기를 개인용으로 전환했습니다."</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"비공개 스페이스 삭제됨"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"조직에서는 이 관리 기기에서 비공개 스페이스를 허용하지 않습니다."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"관리되는 기기"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"조직에서 이 기기를 관리하며 네트워크 트래픽을 모니터링할 수도 있습니다. 자세한 내용을 보려면 탭하세요."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"앱에서 위치 정보에 액세스할 수 있음"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"음성 지원"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"잠금"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"답장"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"새 알림"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"물리적 키보드"</string> <string name="notification_channel_security" msgid="8516754650348238057">"보안"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"방향 패드 왼쪽"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"방향 패드 오른쪽"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"방향 패드 가운데"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>의 자막 표시줄입니다."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> 항목이 RESTRICTED 버킷으로 이동함"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"이미지 보냄"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"작동 방식"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"대기 중…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"지문 잠금 해제 다시 설정"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> 지문이 제대로 작동하지 않아 삭제되었습니다."</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> 및 <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> 지문이 제대로 작동하지 않아 삭제되었습니다."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g>이(가) 제대로 작동하지 않아 삭제되었습니다. 지문으로 휴대전화를 잠금 해제하려면 다시 설정하세요."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> 및 <xliff:g id="FINGERPRINT_1">%2$s</xliff:g>이(가) 제대로 작동하지 않아 삭제되었습니다. 지문으로 휴대전화를 잠금 해제하려면 다시 설정하세요."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"얼굴 인식 잠금 해제 다시 설정"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 8a233a3108a4..9686a141d528 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Жумуш профилиңиз бул түзмөктөн өчүрүлдү"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Өтө көп жолу сырсөздү киргизүү аракети жасалды"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Админ түзмөктөн жеке колдонуу үчүн баш тартты"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Жеке мейкиндик өчүрүлдү"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Уюмуңуз бул көзөмөлдөнгөн түзмөктө жеке мейкиндиктерди колдонууга тыюу салат."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Түзмөктү ишкана башкарат"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын билгиңиз келсе, таптап коюңуз."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Колдонмолор кайда жүргөнүңүздү көрө алат"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Үн жардамчысы"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Бекем кулпулоо"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Жооп берүү"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Жаңы эскертме"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Аппараттык баскычтоп"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Коопсуздук"</string> @@ -1189,8 +1190,8 @@ <string name="deleteText" msgid="4200807474529938112">"Өчүрүү"</string> <string name="inputMethod" msgid="1784759500516314751">"Киргизүү ыкмасы"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"Текст боюнча иштер"</string> - <string name="error_handwriting_unsupported" msgid="7809438534946014050">"Бул талаада жазып киргизүү колдоого алынбайт"</string> - <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"Сырсөз талаасында жазып киргизүү колдоого алынбайт"</string> + <string name="error_handwriting_unsupported" msgid="7809438534946014050">"Бул жерге кол менен жазганга болбойт"</string> + <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"Сырсөз киргизилүүчү жерге кол менен жаза албайсыз"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Артка"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Киргизүү ыкмасын өзгөртүү"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Сактагычта орун калбай баратат"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad\'дын сол баскычы"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad\'дын оң баскычы"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad\'дын ортоңку баскычы"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунун маалымат тилкеси."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ЧЕКТЕЛГЕН чакага коюлган"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"сүрөт жөнөттү"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ал кантип иштейт"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Кезекте турат..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Манжа изи менен ачуу функциясын кайра тууралаңыз"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ойдогудай иштебегендиктен өчүрүлдү"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> жана <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ойдогудай иштебегендиктен өчүрүлдү"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ойдогудай иштебегендиктен, жок кылынды. Телефондо Манжа изи менен ачуу функциясын колдонуу үчүн аны кайра тууралаңыз."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> жана <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ойдогудай иштебегендиктен, жок кылынды. Телефонду манжа изи менен ачуу үчүн аларды кайра тууралаңыз."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Жүзүнөн таанып ачуу функциясын кайрадан тууралаңыз"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index ec58303814d4..b6bd4b2dec51 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກຂອງທ່ານບໍ່ສາມາດໃຊ້ໄດ້ໃນອຸປະກອນນີ້ອີກຕໍ່ໄປ"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ລອງໃສ່ລະຫັດຜ່ານຫຼາຍເທື່ອເກີນໄປ"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"ອຸປະກອນທີ່ຍົກເລີກແລ້ວສຳລັບການໃຊ້ສ່ວນບຸກຄົນ"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"ລຶບພື້ນທີ່ສ່ວນບຸກຄົນອອກແລ້ວ"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"ອົງກອນຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ມີພື້ນທີ່ສ່ວນບຸກຄົນໃນອຸປະກອນທີ່ໄດ້ຮັບການຈັດການນີ້."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"ອຸປະກອນມີການຈັດການ"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"ອົງກອນຂອງທ່ານຈັດການອຸປະກອນນີ້ ແລະ ອາດກວດສອບທຣາບຟິກເຄືອຂ່າຍນຳ. ແຕະເພື່ອເບິ່ງລາຍລະອຽດ."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ແອັບສາມາດເຂົ້າເຖິງສະຖານທີ່ຂອງທ່ານໄດ້"</string> @@ -1188,8 +1190,8 @@ <string name="deleteText" msgid="4200807474529938112">"ລຶບ"</string> <string name="inputMethod" msgid="1784759500516314751">"ຮູບແບບການປ້ອນຂໍ້ມູນ"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"ການເຮັດວຽກຂອງຂໍ້ຄວາມ"</string> - <string name="error_handwriting_unsupported" msgid="7809438534946014050">"ການຂຽນດ້ວຍມືບໍ່ຖືກຮອງຮັບໃນຊ່ອງຂໍ້ມູນນີ້"</string> - <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"ການຂຽນດ້ວຍມືບໍ່ຖືກຮອງຮັບໃນຊ່ອງຂໍ້ມູນລະຫັດຜ່ານ"</string> + <string name="error_handwriting_unsupported" msgid="7809438534946014050">"ບໍ່ຮອງຮັບການຂຽນດ້ວຍມືໃນຊ່ອງຂໍ້ມູນນີ້"</string> + <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"ຊ່ອງຂໍ້ມູນລະຫັດຜ່ານບໍ່ຮອງຮັບການຂຽນດ້ວຍມື"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"ກັບຄືນ"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"ສະຫຼັບວິທີການປ້ອນຂໍ້ມູນ"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"ພື້ນທີ່ຈັດເກັບຂໍ້ມູນກຳລັງຈະເຕັມ"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ຊ້າຍ"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad ຂວາ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad ກາງ"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"ແຖບຄຳບັນຍາຍຂອງ <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ຖືກວາງໄວ້ໃນກະຕ່າ \"ຈຳກັດ\" ແລ້ວ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ສົ່ງຮູບແລ້ວ"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ມັນເຮັດວຽກແນວໃດ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ລໍຖ້າດຳເນີນການ..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ຕັ້ງຄ່າການປົດລັອກດ້ວຍລາຍນິ້ວມືຄືນໃໝ່"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ເຮັດວຽກໄດ້ບໍ່ດີ ແລະ ຖືກລຶບອອກແລ້ວ"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ແລະ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ເຮັດວຽກໄດ້ບໍ່ດີ ແລະ ຖືກລຶບອອກແລ້ວ"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ເຮັດວຽກໄດ້ບໍ່ດີ ແລະ ຖືກລຶບອອກແລ້ວ. ໃຫ້ຕັ້ງຄ່າມັນຄືນໃໝ່ເພື່ອປົດລັອກໂທລະສັບຂອງທ່ານດ້ວຍລາຍນິ້ວມື."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ແລະ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ເຮັດວຽກໄດ້ບໍ່ດີ ແລະ ຖືກລຶບອອກແລ້ວ. ໃຫ້ຕັ້ງຄ່າພວກມັນຄືນໃໝ່ເພື່ອປົດລັອກໂທລະສັບຂອງທ່ານດ້ວຍລາຍນິ້ວມື."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ຕັ້ງຄ່າການປົດລັອກດ້ວຍໜ້າຄືນໃໝ່"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index d3bf781f302b..2b4f370ffc45 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -203,6 +203,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Darbo profilis nebepasiekiamas šiame įrenginyje"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Per daug slaptažodžio bandymų"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratorius atmetė prašymą įrenginį naudoti asmeniniais tikslais"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privati erdvė pašalinta"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Jūsų organizacija neleidžia naudoti privačių erdvių šiame tvarkomame įrenginyje."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Įrenginys yra tvarkomas"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Šį įrenginį tvarko organizacija ir gali stebėti tinklo srautą. Palieskite, kad gautumėte daugiau informacijos."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Programos gali pasiekti jūsų vietovę"</string> @@ -2196,7 +2198,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Valdymo pultas – kairėn"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Valdymo pultas – dešinėn"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Valdymo pultas – centras"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Programos „<xliff:g id="APP_NAME">%1$s</xliff:g>“ antraštės juosta."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"„<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>“ įkeltas į grupę APRIBOTA"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"išsiuntė vaizdą"</string> @@ -2417,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kaip tai veikia"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Laukiama..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Atrakinimo piršto atspaudu nustatymas dar kartą"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> neveikė tinkamai ir buvo ištrintas"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ir <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> neveikė tinkamai ir buvo ištrinti"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> neveikė tinkamai ir buvo ištrintas. Nustatykite jį dar kartą, kad atrakintumėte telefoną piršto atspaudu."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ir <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> neveikė tinkamai ir buvo ištrinti. Nustatykite juos dar kartą, kad atrakintumėte telefoną piršto atspaudu."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Atrakinimo pagal veidą nustatymas iš naujo"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 8be327da1aeb..3669f041f877 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Jūsu darba profils šai ierīcē vairs nav pieejams."</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Veikts pārāk daudz paroles ievadīšanas mēģinājumu."</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrators atteicās no tādas ierīces pārvaldības, ko var izmantot personiskām vajadzībām"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privātā telpa ir noņemta"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Jūsu organizācija neatļauj izmantot privātās telpas šajā pārvaldītajā ierīcē."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Ierīce tiek pārvaldīta"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Jūsu organizācija pārvalda šo ierīci un var uzraudzīt tīkla datplūsmu. Pieskarieties, lai saņemtu detalizētu informāciju."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Lietotne var piekļūt jūsu atrašanās vietas datiem"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Balss palīgs"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloķēšana"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"Pārsniedz"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Atbildēt"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Jauns paziņojums"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziskā tastatūra"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Drošība"</string> @@ -2196,14 +2197,13 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Virzienu slēdzis — pa kreisi"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Virzienu slēdzis — pa labi"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Virzienu slēdzis — centrs"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> subtitru josla."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Pakotne “<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>” ir ievietota ierobežotā kopā."</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"nosūtīts attēls"</string> <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Saruna"</string> <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Grupas saruna"</string> <string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string> - <string name="resolver_personal_tab" msgid="2051260504014442073">"Privātais profils"</string> + <string name="resolver_personal_tab" msgid="2051260504014442073">"Personīgais"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"Darba profils"</string> <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Personisks skats"</string> <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Darba skats"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Darbības principi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Gaida…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Vēlreiz iestatiet autorizāciju ar pirksta nospiedumu"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> nedarbojās pareizi un tika izdzēsts"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> un <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nedarbojās pareizi un tika izdzēsti"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nedarbojās pareizi un tika izdzēsts. Iestatiet to atkal, lai varētu atbloķēt tālruni ar pirksta nospiedumu."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> un <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nedarbojās pareizi un tika izdzēsti. Iestatiet tos atkal, lai varētu atbloķētu tālruni ar pirksta nospiedumu."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Vēlreiz iestatiet autorizāciju pēc sejas"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index d8acc3068c00..8902619d341d 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Вашиот работен профил веќе не е достапен на уредов"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Премногу обиди за внесување лозинка"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Уред откажан од администраторот за лична употреба"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"„Приватниот простор“ е отстранет"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Вашата организација не дозволува „Приватен простор“ на управуваниов уред."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Некој управува со уредот"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Вашата организација управува со уредов и можно е да го следи сообраќајот на мрежата. Допрете за детали."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Апликациите може да пристапуваат до вашата локација"</string> @@ -1188,7 +1190,7 @@ <string name="deleteText" msgid="4200807474529938112">"Избриши"</string> <string name="inputMethod" msgid="1784759500516314751">"Метод на внес"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"Дејства со текст"</string> - <string name="error_handwriting_unsupported" msgid="7809438534946014050">"Ракописот не е поддржан во полево"</string> + <string name="error_handwriting_unsupported" msgid="7809438534946014050">"Ракописот не е поддржан во ова поле"</string> <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"Ракописот не е поддржан во полињата за лозинка"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Назад"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Префрлете го методот за внесување"</string> @@ -2194,14 +2196,13 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Навигациско копче за налево"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Навигациско копче за надесно"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Навигациско копче за средина"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Насловна лента на <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> е ставен во корпата ОГРАНИЧЕНИ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"испрати слика"</string> <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Разговор"</string> <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Групен разговор"</string> <string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string> - <string name="resolver_personal_tab" msgid="2051260504014442073">"Лични"</string> + <string name="resolver_personal_tab" msgid="2051260504014442073">"Лично"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"За работа"</string> <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Личен приказ"</string> <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Работен приказ"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Дознајте како функционира"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Во фаза на чекање…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Поставете „Отклучување со отпечаток“ повторно"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> не функционираше добро, па се избриша"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> и <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> не функционираа добро, па се избришаа"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> не функционираше добро, па се избриша. Поставете го повторно за да го отклучувате телефонот со отпечаток."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> и <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> не функционираа добро, па се избришаа. Поставете ги повторно за да го отклучувате телефонот со отпечаток."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Поставете „Отклучување со лик“ повторно"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 747631ad9cbf..005d7cb5da4f 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ഈ ഉപകരണത്തിൽ തുടർന്നങ്ങോട്ട് നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ ലഭ്യമല്ല"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"വളരെയധികം പാസ്വേഡ് ശ്രമങ്ങൾ"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"വ്യക്തിപരമായ ഉപയോഗത്തിനായി, ഉപകരണത്തിന്റെ ഔദ്യോഗിക ഉപയോഗം അഡ്മിൻ അവസാനിപ്പിച്ചു"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"സ്വകാര്യ സ്പേസ് നീക്കം ചെയ്തു"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"മാനേജ് ചെയ്യപ്പെടുന്ന ഈ ഉപകരണത്തിൽ നിങ്ങളുടെ സ്ഥാപനം സ്വകാര്യ സ്പേസുകൾ അനുവദിക്കുന്നില്ല."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"ഉപകരണം മാനേജുചെയ്യുന്നുണ്ട്"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"നിങ്ങളുടെ സ്ഥാപനമാണ് ഈ ഉപകരണം മാനേജുചെയ്യുന്നത്, നെറ്റ്വർക്ക് ട്രാഫിക്ക് നിരീക്ഷിക്കുകയും ചെയ്തേക്കാം, വിശദാംശങ്ങൾ അറിയാൻ ടാപ്പുചെയ്യുക."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ആപ്പുകൾക്ക് നിങ്ങളുടെ ലൊക്കേഷൻ ആക്സസ് ചെയ്യാനാകും"</string> @@ -2147,7 +2149,7 @@ <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"ബാറ്ററി ലൈഫ് വർദ്ധിപ്പിക്കാൻ ബാറ്ററി ഉപയോഗം കുറയ്ക്കുന്നു"</string> <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"ബാറ്ററി സേവർ ഓണാണ്"</string> <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"ബാറ്ററി ലൈഫ് വർദ്ധിപ്പിക്കാൻ ബാറ്ററി സേവർ ഓണാക്കിയിരിക്കുന്നു"</string> - <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ബാറ്ററി ലാഭിക്കൽ"</string> + <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"ബാറ്ററി സേവർ"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"ബാറ്ററി സേവർ ഓഫാക്കിയിരിക്കുന്നു"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"ഫോണിൽ വേണ്ടത്ര ചാർജ് ഉണ്ട്. ഫീച്ചറുകൾക്ക് ഇനി നിയന്ത്രണമില്ല."</string> <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"ടാബ്ലെറ്റിൽ വേണ്ടത്ര ചാർജ് ഉണ്ട്. ഫീച്ചറുകൾക്ക് ഇനി നിയന്ത്രണമില്ല."</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ലെഫ്റ്റ്"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad റൈറ്റ്"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad സെന്റർ"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിന്റെ അടിക്കുറിപ്പ് ബാർ."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> നിയന്ത്രിത ബക്കറ്റിലേക്ക് നീക്കി"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ചിത്രം അയച്ചു"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ഇത് പ്രവർത്തിക്കുന്നത് എങ്ങനെയാണ്"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"തീർപ്പാക്കിയിട്ടില്ല..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ഫിംഗർപ്രിന്റ് അൺലോക്ക് വീണ്ടും സജ്ജീകരിക്കുക"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ശരിയായി പ്രവർത്തിക്കാത്തതിനാൽ അത് ഇല്ലാതാക്കി"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>, <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> എന്നിവ ശരിയായി പ്രവർത്തിക്കാത്തതിനാൽ അവ ഇല്ലാതാക്കി"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ശരിയായി പ്രവർത്തിക്കാത്തതിനാൽ അത് ഇല്ലാതാക്കി. നിങ്ങളുടെ ഫിംഗർപ്രിന്റ് ഉപയോഗിച്ച് ഫോൺ അൺലോക്ക് ചെയ്യുന്നതിനായി വീണ്ടും സജ്ജീകരിക്കുക."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>, <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> എന്നിവ ശരിയായി പ്രവർത്തിക്കാത്തതിനാൽ അവ ഇല്ലാതാക്കി. നിങ്ങളുടെ ഫിംഗർപ്രിന്റ് ഉപയോഗിച്ച് ഫോൺ അൺലോക്ക് ചെയ്യുന്നതിനായി അവ വീണ്ടും സജ്ജീകരിക്കുക."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ഫെയ്സ് അൺലോക്ക് വീണ്ടും സജ്ജീകരിക്കുക"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 8d430eedc396..ddbeb35cdf31 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Таны ажлын профайл энэ төхөөрөмжид боломжгүй байна"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Нууц үгийг хэт олон удаа буруу оруулсан байна"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Админ хувийн хэрэглээнд зориулж төхөөрөмжийн эрхийг хассан"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Хаалттай орон зайг хассан"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Танай байгууллага энэ удирддаг төхөөрөмж дээр хаалттай орон зайг зөвшөөрдөггүй."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Төхөөрөмжийг удирдсан"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Таны байгууллага энэ төхөөрөмжийг удирдаж, сүлжээний ачааллыг хянадаг. Дэлгэрэнгүй мэдээлэл авах бол товшино уу."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Аппууд нь таны байршилд хандах боломжтой"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Дуут туслах"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Түгжих"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Хариу бичих"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Шинэ мэдэгдэл"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Биет гар"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Аюулгүй байдал"</string> @@ -1190,7 +1191,7 @@ <string name="inputMethod" msgid="1784759500516314751">"Оруулах арга"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"Текст үйлдэл"</string> <string name="error_handwriting_unsupported" msgid="7809438534946014050">"Гараар бичихийг энэ талбарт дэмждэггүй"</string> - <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"Гараар бичихийг нууц үгний талбаруудад дэмждэггүй"</string> + <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"Нууц үгний талбарт гараар бичихийг дэмждэггүй"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Буцах"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Оруулах аргыг сэлгэх"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Сангийн хэмжээ дутагдаж байна"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad зүүн"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad баруун"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad гол"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н гарчгийн талбар."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>-г ХЯЗГААРЛАСАН сагс руу орууллаа"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"зураг илгээсэн"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Энэ хэрхэн ажилладаг вэ?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Хүлээгдэж буй..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Хурууны хээгээр түгжээ тайлахыг дахин тохируулна уу"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> сайн ажиллахгүй байсан тул хурууны хээг устгасан"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> болон <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> сайн ажиллахгүй байсан тул эдгээр хурууны хээг устгасан"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> сайн ажиллахгүй байсан тул үүнийг устгасан. Утасныхаа түгжээг хурууны хээгээр тайлахын тулд хурууны хээг дахин тохируулна уу."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>, <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> сайн ажиллахгүй байсан тул эдгээрийг устгасан. Утасныхаа түгжээг хурууныхаа хээгээр тайлахын тулд хоёр хурууны хээг дахин тохируулна уу."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Царайгаар түгжээ тайлахыг дахин тохируулна уу"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 022f5c3fb88d..77a58aff3157 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"तुमचे कार्य प्रोफाइल आता या डिव्हाइसवर उपलब्ध नाही"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"बर्याचदा पासवर्ड टाकण्याचा प्रयत्न केला"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"वैयक्तिक वापरासाठी ॲडमिनने नियंत्रण सोडलेले डिव्हाइस"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"खाजगी स्पेस काढून टाकली आहे"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"तुमची संस्था या व्यवस्थापित केलेल्या डिव्हाइसवर खाजगी स्पेसना अनुमती देत नाही."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"डिव्हाइस व्यवस्थापित केले आहे"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"तुमची संस्था हे डिव्हाइस व्यवस्थापित करते आणि नेटवर्क रहदारीचे निरीक्षण करू शकते. तपशीलांसाठी टॅप करा."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ॲप्स तुमचे स्थान अॅक्सेस करू शकतात"</string> @@ -1188,8 +1190,8 @@ <string name="deleteText" msgid="4200807474529938112">"हटवा"</string> <string name="inputMethod" msgid="1784759500516314751">"इनपुट पद्धत"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"मजकूर क्रिया"</string> - <string name="error_handwriting_unsupported" msgid="7809438534946014050">"या फील्डमध्ये हस्तलेखनला सपोर्ट नाही"</string> - <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"पासवर्ड फील्डमध्ये हस्तलेखनला सपोर्ट नाही"</string> + <string name="error_handwriting_unsupported" msgid="7809438534946014050">"या फील्डमध्ये हस्तलेखनाला सपोर्ट नाही"</string> + <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"पासवर्ड फील्डमध्ये हस्तलेखनाला सपोर्ट नाही"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"मागे जा"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"इनपुट पद्धत स्विच करा"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"संचयन स्थान संपत आहे"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad डावीकडील"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad चे उजवीकडील"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad चे मधले"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> चा शीर्षक बार."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> हे प्रतिबंधित बादलीमध्ये ठेवण्यात आले आहे"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"इमेज पाठवली आहे"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ते कसे काम करते"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"प्रलंबित आहे..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"फिंगरप्रिंट अनलॉक पुन्हा सेट करा"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> योग्यरीत्या काम करत नव्हती, त्यामुळे ती हटवली आहे"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> आणि <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> योग्यरीत्या काम करत नव्हत्या, त्यामुळे त्या हटवल्या आहेत"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"योग्यरीत्या काम करत नसल्यामुळे <xliff:g id="FINGERPRINT">%s</xliff:g> हटवले गेले आहे. तुमचे फिंगरप्रिंट वापरून फोन अनलॉक करण्यासाठी ते पुन्हा सेट करा."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"परफॉर्मन्समध्ये सुधारणा करण्यासाठी आणि योग्यरीत्या काम करत नसल्यामुळे <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> व <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> हटवली गेली आहेत. तुमचे फिंगरप्रिंट वापरून फोन अनलॉक करण्यासाठी ते पुन्हा सेट करा."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"फेस अनलॉक पुन्हा सेट करा"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 908f5a84f453..96691f60b151 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profil kerja anda tidak lagi tersedia pada peranti ini"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Terlalu banyak percubaan kata laluan"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Pentadbir melepaskan peranti untuk kegunaan peribadi"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Ruang privasi dialih keluar"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Organisasi anda tidak membenarkan ruang privasi pada peranti terurus ini."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Peranti ini diurus"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organisasi anda mengurus peranti ini dan mungkin memantau trafik rangkaian. Ketik untuk mendapatkan butiran."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apl boleh mengakses lokasi anda"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Kiri"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Kanan"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Tengah"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Bar kapsyen <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> telah diletakkan dalam baldi TERHAD"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"menghantar imej"</string> @@ -2403,7 +2404,7 @@ <string name="profile_label_test" msgid="9168641926186071947">"Ujian"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Umum"</string> <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil kerja"</string> - <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ruang privasi"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ruang persendirian"</string> <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Umum"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Kandungan pemberitahuan yang sensitif disembunyikan"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara ciri ini berfungsi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Belum selesai..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Sediakan Buka Kunci Cap Jari sekali lagi"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> tidak berfungsi dengan baik dan telah dipadamkan"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> dan <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> tidak berfungsi dengan baik dan telah dipadamkan"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> tidak berfungsi dengan baik dan telah dipadamkan. Sediakan cap jari sekali lagi untuk membuka kunci telefon anda menggunakan cap jari."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> dan <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> tidak berfungsi dengan baik dan telah dipadamkan. Sediakan kedua-dua cap jari tersebut sekali lagi untuk membuka kunci telefon anda menggunakan cap jari anda."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Sediakan semula Buka Kunci Wajah"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 22fe7f231db1..c1b782236a65 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ဤစက်ပစ္စည်းတွင် သင်၏ အလုပ်ပရိုဖိုင်မရှိတော့ပါ"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"စကားဝှက်ထည့်သွင်းရန် ကြိုးစားသည့် အကြိမ်အရေအတွက် အလွန်များသွား၍ ဖြစ်ပါသည်"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"ပုဂ္ဂိုလ်ရေးအသုံးပြုရန်အတွက် စီမံခန့်ခွဲသူက စက်ပစ္စည်းထိန်းချုပ်မှုကို ရပ်တန့်လိုက်သည်"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"သီးသန့်နေရာကို ဖယ်ရှားလိုက်သည်"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"သင့်အဖွဲ့အစည်းသည် ကြီးကြပ်ထားသော ဤစက်ပေါ်တွင် သီးသန့်နေရာများကို ခွင့်မပြုပါ။"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"စက်ပစ္စည်းကို စီမံခန့်ခွဲထားပါသည်"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"ဤစက်ပစ္စည်းကို သင်၏ အဖွဲ့အစည်းက စီမံပြီး ကွန်ရက်အသွားအလာကို စောင့်ကြည့်နိုင်ပါသည်။ ထပ်မံလေ့လာရန် တို့ပါ။"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"အက်ပ်များက သင်၏တည်နေရာကို ကြည့်နိုင်သည်"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ဘယ်"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad ညာ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad အလယ်"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>၏ ခေါင်းစီး ဘား။"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ကို တားမြစ်ထားသော သိမ်းဆည်းမှုအတွင်းသို့ ထည့်ပြီးပါပြီ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>-"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ပုံပို့ထားသည်"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"အလုပ်လုပ်ပုံ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ဆိုင်းငံ့ထားသည်…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"‘လက်ဗွေသုံး လော့ခ်ဖွင့်ခြင်း’ ကို စနစ်ထပ်မံထည့်သွင်းပါ"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> သိပ်အဆင်မပြေသဖြင့် ဖျက်ထားသည်"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> နှင့် <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> တို့ သိပ်အဆင်မပြေသဖြင့် ဖျက်ထားသည်"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> သိပ်အဆင်မပြေသဖြင့် ဖျက်ထားသည်။ သင့်ဖုန်းကို လက်ဗွေဖြင့်လော့ခ်ဖွင့်ရန် ၎င်းကို စနစ်ထပ်မံထည့်သွင်းပါ။"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> နှင့် <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> တို့ သိပ်အဆင်မပြေသဖြင့် ဖျက်ထားသည်။ သင့်ဖုန်းကို လက်ဗွေဖြင့်လော့ခ်ဖွင့်ရန် ၎င်းတို့ကို စနစ်ထပ်မံထည့်သွင်းပါ။"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"‘မျက်နှာပြ လော့ခ်ဖွင့်ခြင်း’ ကို စနစ်ထပ်မံထည့်သွင်းပါ"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 3939f48b9265..23f675e73f78 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Jobbprofilen din er ikke lenger tilgjengelig på denne enheten"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"For mange passordforsøk"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratoren overførte enheten til personlig bruk"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Det private området er fjernet"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Organisasjonen din tillater ikke private områder på denne administrerte enheten."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Enheten administreres"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organisasjonen din kontrollerer denne enheten og kan overvåke nettverkstrafikk. Trykk for å få mer informasjon."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apper har tilgang til posisjonen din"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Talehjelp"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Låsing"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Svar"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Nytt varsel"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysisk tastatur"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sikkerhet"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Venstre på styrepilene"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Høyre på styrepilene"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Midt på styrepilene"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Tekstingsfelt i <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> er blitt plassert i TILGANGSBEGRENSET-toppmappen"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"har sendt et bilde"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Slik fungerer det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Venter …"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfigurer opplåsingen med fingeravtrykk på nytt"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> fungerte ikke skikkelig og ble slettet"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> fungerte ikke skikkelig og ble slettet"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> fungerte ikke skikkelig og ble slettet. Du kan konfigurere det på nytt for å låse opp telefonen med fingeravtrykket."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> fungerte ikke skikkelig og ble slettet. Du kan konfigurere dem på nytt for å låse opp telefonen med fingeravtrykket."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfigurer ansiktslåsen på nytt"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 3253afa2e67e..99dd0e4c2c9f 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"तपाईंको कार्य प्रोफाइल अब उप्रान्त यस डिभाइसमा उपलब्ध छैन"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"पासवर्ड प्रविष्ट गर्ने अत्यधिक गलत प्रयासहरू भए"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"व्यवस्थापकले यन्त्रलाई व्यक्तिगत प्रयोगका लागि अस्वीकार गर्नुभयो"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"निजी स्पेस हटाइएको छ"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"तपाईंको सङ्गठन आफूले व्यवस्थापन गरेको यो डिभाइसमा निजी स्पेस राख्ने अनुमति दिँदैन।"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"यन्त्र व्यवस्थित गरिएको छ"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"तपाईंको संगठनले यस डिभाइसको व्यवस्थापन गर्दछ र नेटवर्क ट्राफिकको अनुगमन गर्न सक्छ। विवरणहरूका लागि ट्याप गर्नुहोस्।"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"एपहरूले तपाईंको स्थान प्रयोग गर्न सक्छन्"</string> @@ -1171,7 +1173,7 @@ <string name="elapsed_time_short_format_mm_ss" msgid="8689459651807876423">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="2302144714803345056">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="1532369154488982046">"सबैलाई चयन गर्नुहोस्"</string> - <string name="cut" msgid="2561199725874745819">"काट्नुहोस्"</string> + <string name="cut" msgid="2561199725874745819">"कट् गर्नुहोस्"</string> <string name="copy" msgid="5472512047143665218">"कपी गर्नुहोस्"</string> <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"क्लिपबोर्डमा कपी गर्न सकिएन"</string> <string name="paste" msgid="461843306215520225">"टाँस्नुहोस्"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad को बायाँको बटन"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad को दायाँको बटन"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad को बिचको बटन"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> को क्याप्सन बार।"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> लाई प्रतिबन्धित बाल्टीमा राखियो"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"छवि पठाइयो"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यसले काम गर्ने तरिका"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"विचाराधीन..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"फिंगरप्रिन्ट अनलक फेरि सेटअप गर्नुहोस्"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"राम्ररी काम नगरिरहेको हुनाले <xliff:g id="FINGERPRINT">%s</xliff:g> मेटाइएको छ"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"राम्ररी काम नगरिरहेका हुनाले <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> र <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> मेटाइएका छन्"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ले काम गरिरहेको थिएन र त्यसलाई मेटाइयो। फिंगरप्रिन्ट प्रयोग गरी आफ्नो फोन अनलक गर्न त्यसलाई फेरि सेट अप गर्नुहोस्।"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> र <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ले राम्ररी काम गरिरहेका थिएनन् र तिनलाई मेटाइयो। फिंगरप्रिन्ट प्रयोग गरी आफ्नो फोन अनलक गर्न तिनलाई फेरि सेट अप गर्नुहोस्।"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"फेस अनलक फेरि सेटअप गर्नुहोस्"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 6ece5748b7f3..0b2c507b644f 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Je werkprofiel is niet meer beschikbaar op dit apparaat"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Te veel wachtwoordpogingen"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"De beheerder heeft het apparaat afgestaan voor persoonlijk gebruik"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privégedeelte verwijderd"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Je organisatie staat geen privégedeelten toe op dit beheerde apparaat."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Apparaat wordt beheerd"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Dit apparaat wordt beheerd door je organisatie. Het netwerkverkeer kan worden bijgehouden. Tik voor meer informatie."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Apps hebben toegang tot je locatie"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"D-pad links"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"D-pad rechts"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"D-pad midden"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Ondertitelingsbalk van <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> is in de bucket RESTRICTED geplaatst"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"heeft een afbeelding gestuurd"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe het werkt"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"In behandeling…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ontgrendelen met vingerafdruk weer instellen"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> werkte niet goed en is verwijderd"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> en <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> werkten niet goed en zijn verwijderd"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> werkte niet goed en is verwijderd. Stel deze opnieuw in om de telefoon met je vingerafdruk te ontgrendelen."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> en <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> werkten niet goed en zijn verwijderd. Stel ze opnieuw in om de telefoon met je vingerafdruk te ontgrendelen."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Ontgrendelen via gezichtsherkenning weer instellen"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index ae67a743f142..f3cd70e83db1 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ଏହି ଡିଭାଇସରେ ଆପଣଙ୍କ ୱର୍କ ପ୍ରୋଫାଇଲ୍ ଆଉ ଉପଲବ୍ଧ ନାହିଁ"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ବହୁତ ଥର ଭୁଲ ପାସ୍ୱର୍ଡ ଲେଖିଛନ୍ତି"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"ବ୍ୟକ୍ତିଗତ ବ୍ୟବହାର ପାଇଁ ଆଡ୍ମିନ୍ ଡିଭାଇସ୍କୁ ଅଲଗା କରିଛନ୍ତି"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"ପ୍ରାଇଭେଟ ସ୍ପେସ କାଢ଼ି ଦିଆଯାଇଛି"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"ଆପଣଙ୍କ ସଂସ୍ଥା ଏହି ପରିଚାଳିତ ଡିଭାଇସରେ ପ୍ରାଇଭେଟ ସ୍ପେସକୁ ଅନୁମତି ଦିଏ ନାହିଁ।"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"ଡିଭାଇସକୁ ପରିଚାଳନା କରାଯାଉଛି"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"ଆପଣଙ୍କ ସଂସ୍ଥା ଏହି ଡିଭାଇସକୁ ପରିଚାଳନା କରନ୍ତି ଏବଂ ନେଟୱର୍କ ଟ୍ରାଫିକ୍ ନୀରିକ୍ଷଣ କରନ୍ତି। ବିବରଣୀ ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ଆପଗୁଡ଼ିକ ଆପଣଙ୍କ ଲୋକେସନକୁ ଆକ୍ସେସ୍ କରିପାରିବ"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ବାମ"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad ଡାହାଣ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad କେନ୍ଦ୍ର"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>ର କ୍ୟାପ୍ସନ୍ ବାର୍।"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>କୁ ପ୍ରତିବନ୍ଧିତ ବକେଟରେ ରଖାଯାଇଛି"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ଏକ ଛବି ପଠାଯାଇଛି"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ଏହା କିପରି କାମ କରେ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ବାକି ଅଛି…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ଫିଙ୍ଗରପ୍ରିଣ୍ଟ ଅନଲକ ପୁଣି ସେଟ ଅପ କରନ୍ତୁ"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ସଠିକ ଭାବେ କାମ କରୁନାହିଁ ଏବଂ ଏହାକୁ ଡିଲିଟ କରାଯାଇଛି"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ଏବଂ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ସଠିକ ଭାବେ କାମ କରୁନାହିଁ ଏବଂ ଏଗୁଡ଼ିକୁ ଡିଲିଟ କରାଯାଇଛି"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ସଠିକ ଭାବେ କାମ କରୁନାହିଁ ଏବଂ ଏହାକୁ ଡିଲିଟ କରାଯାଇଛି। ଟିପଚିହ୍ନ ମାଧ୍ୟମରେ ଆପଣଙ୍କ ଫୋନକୁ ଅନଲକ କରିବାକୁ ଏହାକୁ ପୁଣି ସେଟ ଅପ କରନ୍ତୁ।"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ଏବଂ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ସଠିକ ଭାବେ କାମ କରୁନାହିଁ ଏବଂ ଏଗୁଡ଼ିକୁ ଡିଲିଟ କରାଯାଇଛି। ଆପଣଙ୍କ ଟିପଚିହ୍ନ ମାଧ୍ୟମରେ ଆପଣଙ୍କ ଫୋନକୁ ଅନଲକ କରିବାକୁ ଏଗୁଡ଼ିକୁ ପୁଣି ସେଟ ଅପ କରନ୍ତୁ।"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ଫେସ୍ ଅନଲକ୍ ପୁଣି ସେଟ୍ ଅପ୍ କରନ୍ତୁ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 5e79a7c01301..60d8737405da 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ਤੁਹਾਡਾ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਹੁਣ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ਕਈ ਵਾਰ ਗਲਤ ਪਾਸਵਰਡ ਦਾਖਲ ਕੀਤਾ ਗਿਆ"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"ਪ੍ਰਸ਼ਾਸਕ ਨੇ ਨਿੱਜੀ ਵਰਤੋਂ ਲਈ ਡੀਵਾਈਸ ਤਿਆਗਿਆ"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਨੂੰ ਹਟਾਇਆ ਗਿਆ"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"ਤੁਹਾਡੀ ਸੰਸਥਾ ਇਸ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਡੀਵਾਈਸ \'ਤੇ ਪ੍ਰਾਈਵੇਟ ਸਪੇਸਾਂ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੰਦੀ।"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"ਡੀਵਾਈਸ ਪ੍ਰਬੰਧਨ ਅਧੀਨ ਹੈ"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"ਤੁਹਾਡਾ ਸੰਗਠਨ ਇਸ ਡੀਵਾਈਸ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਦਾ ਹੈ ਅਤੇ ਨੈੱਟਵਰਕ ਟਰੈਫਿਕ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦਾ ਹੈ। ਵੇਰਵਿਆਂ ਲਈ ਟੈਪ ਕਰੋ।"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ਐਪਾਂ ਤੁਹਾਡੇ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀਆਂ ਹਨ"</string> @@ -835,11 +837,11 @@ <string name="policylab_watchLogin" msgid="7599669460083719504">"ਸਕ੍ਰੀਨ ਅਣਲਾਕ ਕਰਨ ਦੀਆਂ ਕੋਸ਼ਿਸ਼ਾਂ \'ਤੇ ਨਿਗਰਾਨੀ ਰੱਖਣਾ"</string> <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰਨਾ ਅਤੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਵਾਰ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਜਾਣ \'ਤੇ ਟੈਬਲੈੱਟ ਨੂੰ ਲਾਕ ਕਰਨਾ ਜਾਂ ਟੈਬਲੈੱਟ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਉਣਾ।"</string> <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਆਪਣੇ Android TV ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਆਪਣੇ Android TV ਡੀਵਾਈਸ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ, ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string> - <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ ਅਤੇ ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ, ਤਾਂ ਵਾਹਨ ਆਡੀਓ ਸਿਸਟਮ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਵਾਹਨ ਆਡੀਓ ਸਿਸਟਮ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> + <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ ਅਤੇ ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ, ਤਾਂ ਇੰਫ਼ੋਟੇਨਮੈਂਟ ਸਿਸਟਮ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਇੰਫ਼ੋਟੇਨਮੈਂਟ ਸਿਸਟਮ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਗਿਣਤੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ ਅਤੇ ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ, ਤਾਂ ਫ਼ੋਨ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਫ਼ੋਨ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਟੈਬਲੈੱਟ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਟੈਬਲੈੱਟ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ, ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string> <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਆਪਣੇ Android TV ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਇਸ ਵਰਤੋਂਕਾਰ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ, ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string> - <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ ਅਤੇ ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ, ਤਾਂ ਵਾਹਨ ਆਡੀਓ ਸਿਸਟਮ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਇਸ ਪ੍ਰੋਫਾਈਲ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> + <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ ਅਤੇ ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ, ਤਾਂ ਇੰਫ਼ੋਟੇਨਮੈਂਟ ਸਿਸਟਮ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਇਸ ਪ੍ਰੋਫਾਈਲ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਫ਼ੋਨ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਫ਼ੋਨ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string> <string name="policylab_resetPassword" msgid="214556238645096520">"ਸਕ੍ਰੀਨ ਲਾਕ ਬਦਲੋ"</string> <string name="policydesc_resetPassword" msgid="4626419138439341851">"ਸਕ੍ਰੀਨ ਲਾਕ ਬਦਲੋ।"</string> @@ -848,13 +850,13 @@ <string name="policylab_wipeData" msgid="1359485247727537311">"ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਉਣਾ"</string> <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰ ਕੇ ਚਿਤਾਵਨੀ ਤੋਂ ਬਿਨਾਂ ਟੈਬਲੈੱਟ ਦਾ ਡਾਟਾ ਮਿਟਾਉਣਾ।"</string> <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰਕੇ ਬਿਨਾਂ ਚਿਤਾਵਨੀ ਦੇ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ ਦਾ ਡਾਟਾ ਮਿਟਾ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ।"</string> - <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰਕੇ ਚਿਤਾਵਨੀ ਤੋਂ ਬਿਨਾਂ ਵਾਹਨ ਆਡੀਓ ਸਿਸਟਮ ਦਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> + <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰਕੇ ਚਿਤਾਵਨੀ ਤੋਂ ਬਿਨਾਂ ਇੰਫ਼ੋਟੇਨਮੈਂਟ ਸਿਸਟਮ ਦਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰ ਕੇ ਚਿਤਾਵਨੀ ਤੋਂ ਬਿਨਾਂ ਫ਼ੋਨ ਦਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"ਪ੍ਰੋਫਾਈਲ ਡਾਟਾ ਮਿਟਾਓ"</string> <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"ਉਪਭੋਗਤਾ ਡਾਟਾ ਮਿਟਾਓ"</string> <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"ਬਿਨਾਂ ਚਿਤਾਵਨੀ ਦੇ ਇਸ ਟੈਬਲੈੱਟ ਤੇ ਮੌਜੂਦ ਇਸ ਵਰਤੋਂਕਾਰ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"ਬਿਨਾਂ ਚਿਤਾਵਨੀ ਦੇ ਇਸ Android TV ਡੀਵਾਈਸ \'ਤੇ ਮੌਜੂਦ ਇਸ ਵਰਤੋਂਕਾਰ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> - <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"ਇਸ ਵਾਹਨ ਆਡੀਓ ਸਿਸਟਮ \'ਤੇ ਚਿਤਾਵਨੀ ਤੋਂ ਬਿਨਾਂ ਇਸ ਪ੍ਰੋਫਾਈਲ ਦਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> + <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"ਇਸ ਇੰਫ਼ੋਟੇਨਮੈਂਟ ਸਿਸਟਮ \'ਤੇ ਚਿਤਾਵਨੀ ਤੋਂ ਬਿਨਾਂ ਇਸ ਪ੍ਰੋਫਾਈਲ ਦਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"ਬਿਨਾਂ ਚਿਤਾਵਨੀ ਦੇ ਇਸ ਫ਼ੋਨ ਤੇ ਮੌਜੂਦ ਇਸ ਵਰਤੋਂਕਾਰ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policylab_setGlobalProxy" msgid="215332221188670221">"ਡੀਵਾਈਸ ਗਲੋਬਲ ਪ੍ਰੌਕਸੀ ਸੈੱਟ ਕਰੋ"</string> <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"ਜਦੋਂ ਨੀਤੀ ਚਾਲੂ ਹੋਵੇ ਤਾਂ ਵਰਤੇ ਜਾਣ ਲਈ ਡੀਵਾਈਸ ਗਲੋਬਲ ਪ੍ਰੌਕਸੀ ਸੈੱਟ ਕਰੋ। ਕੇਵਲ ਡੀਵਾਈਸ ਮਾਲਕ ਗਲੋਬਲ ਪ੍ਰੌਕਸੀ ਸੈੱਟ ਕਰ ਸਕਦਾ ਹੈ।"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ਦਾ ਖੱਬੇ ਪਾਸੇ ਵਾਲਾ ਬਟਨ"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad ਦਾ ਸੱਜੇ ਪਾਸੇ ਵਾਲਾ ਬਟਨ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad ਦਾ ਵਿਚਕਾਰਲਾ ਬਟਨ"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਦੀ ਸੁਰਖੀ ਪੱਟੀ।"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ਨੂੰ ਪ੍ਰਤਿਬੰਧਿਤ ਖਾਨੇ ਵਿੱਚ ਪਾਇਆ ਗਿਆ ਹੈ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ਚਿੱਤਰ ਭੇਜਿਆ ਗਿਆ"</string> @@ -2222,7 +2223,7 @@ <string name="miniresolver_call_information" msgid="6739417525304184083">"ਤੁਹਾਡੀ ਸੰਸਥਾ ਤੁਹਾਨੂੰ ਸਿਰਫ਼ ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਤੋਂ ਕਾਲਾਂ ਕਰਨ ਦਿੰਦੀ ਹੈ"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"ਤੁਹਾਡੀ ਸੰਸਥਾ ਤੁਹਾਨੂੰ ਸਿਰਫ਼ ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਤੋਂ ਹੀ ਸੁਨੇਹੇ ਭੇਜਣ ਦਿੰਦੀ ਹੈ"</string> <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"ਤੁਸੀਂ ਸਿਰਫ਼ ਆਪਣੀ ਨਿੱਜੀ ਫ਼ੋਨ ਐਪ ਤੋਂ ਫ਼ੋਨ ਕਾਲਾਂ ਕਰ ਸਕਦੇ ਹੋ। ਨਿੱਜੀ ਫ਼ੋਨ ਤੋਂ ਕੀਤੀਆਂ ਕਾਲਾਂ ਤੁਹਾਡੇ ਨਿੱਜੀ ਕਾਲ ਇਤਿਹਾਸ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤੀਆਂ ਜਾਣਗੀਆਂ।"</string> - <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"ਤੁਸੀਂ ਸਿਰਫ਼ ਆਪਣੀ ਨਿੱਜੀ ਸੁਨੇਹਾ ਐਪ ਤੋਂ SMS ਸੁਨੇਹੇ ਭੇਜ ਸਕਦੇ ਹੋ।"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"ਤੁਸੀਂ ਸਿਰਫ਼ ਆਪਣੀ ਪ੍ਰਾਈਵੇਟ ਸੁਨੇਹਾ ਐਪ ਤੋਂ SMS ਸੁਨੇਹੇ ਭੇਜ ਸਕਦੇ ਹੋ।"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ਨਿੱਜੀ ਬ੍ਰਾਊਜ਼ਰ ਵਰਤੋ"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"ਕੰਮ ਸੰਬੰਧੀ ਬ੍ਰਾਊਜ਼ਰ ਵਰਤੋ"</string> <string name="miniresolver_call" msgid="6386870060423480765">"ਕਾਲ ਕਰੋ"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ਇਹ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ਵਿਚਾਰ-ਅਧੀਨ..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਅਣਲਾਕ ਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ਚੰਗੀ ਤਰ੍ਹਾਂ ਕੰਮ ਨਹੀਂ ਕਰ ਰਿਹਾ ਸੀ ਅਤੇ ਉਸਨੂੰ ਮਿਟਾਇਆ ਗਿਆ ਸੀ"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ਅਤੇ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ਚੰਗੀ ਤਰ੍ਹਾਂ ਕੰਮ ਨਹੀਂ ਕਰ ਰਹੇ ਸਨ ਅਤੇ ਉਨ੍ਹਾਂ ਨੂੰ ਮਿਟਾਇਆ ਗਿਆ ਸੀ"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ਚੰਗੀ ਤਰ੍ਹਾਂ ਕੰਮ ਨਹੀਂ ਕਰ ਰਿਹਾ ਸੀ ਅਤੇ ਉਸਨੂੰ ਮਿਟਾਇਆ ਗਿਆ ਸੀ। ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਫਿੰਗਰਪ੍ਰਿੰਟ ਨਾਲ ਅਣਲਾਕ ਕਰਨ ਲਈ ਇਸਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ।"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ਅਤੇ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ਚੰਗੀ ਤਰ੍ਹਾਂ ਕੰਮ ਨਹੀਂ ਕਰ ਰਹੇ ਸੀ ਅਤੇ ਉਨ੍ਹਾਂ ਨੂੰ ਮਿਟਾਇਆ ਗਿਆ ਸੀ। ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ ਨਾਲ ਅਣਲਾਕ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ।"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ਫ਼ੇਸ ਅਣਲਾਕ ਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index d810710a276d..ec5fee76d73d 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -203,6 +203,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Twój profil służbowy nie jest już dostępny na tym urządzeniu"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Zbyt wiele prób podania hasła"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator odstąpił urządzenie do użytku osobistego"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Przestrzeń prywatna została usunięta"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Twoja organizacja nie zezwala na przestrzenie prywatne na tym urządzeniu zarządzanym."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Urządzenie jest zarządzane"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Twoja organizacja zarządza tym urządzeniem i może monitorować ruch w sieci. Kliknij, by dowiedzieć się więcej."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikacje mogą mieć dostęp do Twojej lokalizacji"</string> @@ -2196,7 +2198,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad – w lewo"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad – w prawo"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad – środek"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Pasek napisów w aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Umieszczono pakiet <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> w zasobniku danych RESTRICTED"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"wysłano obraz"</string> @@ -2417,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to działa"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Oczekiwanie…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Skonfiguruj ponownie odblokowywanie odciskiem palca"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Odcisk palca <xliff:g id="FINGERPRINT">%s</xliff:g> nie sprawdzał się dobrze i został usunięty"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Odciski palców <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nie sprawdzały się dobrze i zostały usunięte"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Odcisk palca <xliff:g id="FINGERPRINT">%s</xliff:g> nie sprawdzał się dobrze i został usunięty. Skonfiguruj go ponownie, aby odblokowywać telefon odciskiem palca."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Odciski palca <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nie sprawdzały się dobrze i zostały usunięte. Skonfiguruj je ponownie, aby odblokowywać telefon odciskiem palca."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Skonfiguruj ponownie rozpoznawanie twarzy"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index e3b49a0b399b..7067a89b2621 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Seu perfil de trabalho não está mais disponível neste dispositivo"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Muitas tentativas de senha"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador renunciou ao dispositivo para uso pessoal"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Espaço privado removido"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Sua organização não permite espaços privados neste dispositivo gerenciado."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"O dispositivo é gerenciado"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Sua organização gerencia este dispositivo e pode monitorar o tráfego de rede. Toque para ver detalhes."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Os apps podem acessar seu local"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ajuda de voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueio total"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Responder"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Botão direcional: para a esquerda"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Botão direcional: para a direita"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Botão direcional: centro"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de legendas do app <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> foi colocado no intervalo \"RESTRITO\""</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviou uma imagem"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configurar o Desbloqueio por impressão digital de novo"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"A impressão digital <xliff:g id="FINGERPRINT">%s</xliff:g> não estava funcionando bem e foi excluída"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"As impressões digitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam funcionando bem e foram excluídas"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A impressão digital <xliff:g id="FINGERPRINT">%s</xliff:g> não estava funcionando bem e foi excluída. Configure de novo para desbloquear o smartphone com a impressão digital."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"As impressões digitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam funcionando bem e foram excluídas. Configure de novo para desbloquear o smartphone com a impressão digital."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Configure o Desbloqueio facial de novo"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 512f6976fff5..6632f85b30fd 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"O seu perfil de trabalho já não está disponível neste dispositivo"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Demasiadas tentativas de introdução da palavra-passe"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador anulou o dispositivo para utilização pessoal."</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Espaço privado removido"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"A sua organização não permite espaços privados neste dispositivo gerido."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"O dispositivo é gerido"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"A sua entidade gere este dispositivo e pode monitorizar o tráfego de rede. Toque para obter mais detalhes."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"As apps podem aceder à sua localização"</string> @@ -2195,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Teclado direcional: para a esquerda"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Teclado direcional: para a direita"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Teclado direcional: centrar"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de legendas da app <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> foi colocado no contentor RESTRITO."</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviou uma imagem"</string> @@ -2416,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configure o Desbloqueio por impressão digital novamente"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"A <xliff:g id="FINGERPRINT">%s</xliff:g> não estava a funcionar bem e foi eliminada"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"A <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam a funcionar bem e foram eliminadas"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A <xliff:g id="FINGERPRINT">%s</xliff:g> não estava a funcionar bem e foi eliminada. Configure-a novamente para desbloquear o telemóvel com a impressão digital."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"A <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam a funcionar bem e foram eliminadas. Configure-as novamente para desbloquear o telemóvel com a sua impressão digital."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Configure o Desbloqueio facial novamente"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index e3b49a0b399b..7067a89b2621 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Seu perfil de trabalho não está mais disponível neste dispositivo"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Muitas tentativas de senha"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador renunciou ao dispositivo para uso pessoal"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Espaço privado removido"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Sua organização não permite espaços privados neste dispositivo gerenciado."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"O dispositivo é gerenciado"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Sua organização gerencia este dispositivo e pode monitorar o tráfego de rede. Toque para ver detalhes."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Os apps podem acessar seu local"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ajuda de voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueio total"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Responder"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Botão direcional: para a esquerda"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Botão direcional: para a direita"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Botão direcional: centro"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de legendas do app <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> foi colocado no intervalo \"RESTRITO\""</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviou uma imagem"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configurar o Desbloqueio por impressão digital de novo"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"A impressão digital <xliff:g id="FINGERPRINT">%s</xliff:g> não estava funcionando bem e foi excluída"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"As impressões digitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam funcionando bem e foram excluídas"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A impressão digital <xliff:g id="FINGERPRINT">%s</xliff:g> não estava funcionando bem e foi excluída. Configure de novo para desbloquear o smartphone com a impressão digital."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"As impressões digitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam funcionando bem e foram excluídas. Configure de novo para desbloquear o smartphone com a impressão digital."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Configure o Desbloqueio facial de novo"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 7de995219497..bf82cd8cb5b7 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profilul de serviciu nu mai este disponibil pe acest dispozitiv"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Prea multe încercări de introducere a parolei"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratorul a retras dispozitivul pentru uz personal"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Spațiul privat a fost eliminat"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Organizația ta nu permite spațiile private pe acest dispozitiv gestionat."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Dispozitivul este gestionat"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organizația ta gestionează acest dispozitiv și poate monitoriza traficul în rețea. Atinge pentru mai multe detalii."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplicațiile îți pot accesa locația"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Asistent vocal"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blocare strictă"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"˃999"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Răspunde"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificare nouă"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastatură fizică"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Securitate"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad stânga"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad dreapta"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad centru"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Bară cu legenda pentru <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> a fost adăugat la grupul RESTRICȚIONATE"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"a trimis o imagine"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cum funcționează"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"În așteptare..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configurează din nou Deblocarea cu amprenta"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> nu funcționa bine și a fost ștearsă"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> și <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nu funcționau bine și au fost șterse"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nu funcționa bine și s-a șters. Configureaz-o din nou pentru a-ți debloca telefonul cu amprenta."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> și <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nu funcționau bine și s-au șters. Configurează-le din nou pentru a-ți debloca telefonul cu amprenta."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Reconfigurează Deblocarea facială"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index d999bf5b2050..c1900a86487a 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -203,6 +203,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Ваш рабочий профиль больше не доступен на этом устройстве"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Слишком много попыток ввести пароль."</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Администратор освободил устройство для личного использования"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Частное пространство удалено"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Ваша организация запрещает использовать частное пространство на этом управляемом устройстве."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Это управляемое устройство"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Ваша организация управляет этим устройством и может отслеживать сетевой трафик. Подробнее…"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"У приложений есть доступ к вашим геоданным"</string> @@ -285,8 +287,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Аудиоподсказки"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Блокировка входа"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Ответить"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Новое уведомление"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физическая клавиатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Безопасность"</string> @@ -2197,14 +2198,13 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"D-pad – влево"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"D-pad – вправо"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"D-pad – по центру"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Строка субтитров в приложении \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Приложение \"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>\" помещено в категорию с ограниченным доступом."</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"Отправлено изображение"</string> <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Чат"</string> <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Групповой чат"</string> <string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string> - <string name="resolver_personal_tab" msgid="2051260504014442073">"Личное"</string> + <string name="resolver_personal_tab" msgid="2051260504014442073">"Личный"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"Рабочее"</string> <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Просмотр личных данных"</string> <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Просмотр рабочих данных"</string> @@ -2418,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Узнать принцип работы"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Обработка…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Настройте разблокировку по отпечатку пальца заново"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Отпечаток пальца \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" оказался неудачным и был удален."</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Отпечатки пальцев \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" и \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" оказались неудачными и были удалены."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Отпечаток пальца \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" оказался неудачным и был удален. Чтобы использовать разблокировку с помощью отпечатка пальца, настройте ее заново."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Отпечатки пальцев \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" и \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" оказались неудачными и были удалены. Чтобы использовать разблокировку с помощью отпечатка пальца, настройте ее заново."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Настройте фейсконтроль заново"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index a8eda4f65c7e..f216ce2da9fe 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ඔබේ කාර්යාල පැතිකඩ මෙම උපාංගය මත තවදුරටත් ලබා ගැනීමට නොහැකිය"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"මුරපද උත්සාහ කිරීම් ඉතා වැඩි ගණනකි"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"පරිපාලක පුද්ගලික භාවිතය සඳහා උපාංගය අත්හැර දමන ලදී"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"පුද්ගලික ඉඩ ඉවත් කරන ලදි"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"ඔබේ සංවිධානය මෙම කළමනා කෙරෙන උපාංගය මත පුද්ගලික ඉඩවලට ඉඩ නොදෙයි."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"උපාංගය කළමනාකරණය කෙරේ"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"ඔබගේ ආයතනය මෙම උපාංගය කළමනාකරණය කරන අතර එය ජාල තදබදය නිරීක්ෂණය කළ හැක. විස්තර සඳහා තට්ටු කරන්න."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"යෙදුම්වලට ඔබේ ස්ථානයට ප්රවේශ විය හැකිය"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"හඬ සහායක"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"අගුලු දැමීම"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"පිළිතුර"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"නව දැනුම්දීම"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"භෞතික යතුරු පුවරුව"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ආරක්ෂාව"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad වම"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad දකුණ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad මැද"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> හි සිරස්තල තීරුව."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> අවහිර කළ බාල්දියට දමා ඇත"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"රූපයක් එව්වා"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"එය ක්රියා කරන ආකාරය"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"පොරොත්තුයි..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ඇඟිලි සලකුණු අගුලු හැරීම නැවත සකසන්න"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> හොඳින් ක්රියා නොකළ අතර එය මකන ලදි"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> සහ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> හොඳින් ක්රියා නොකළ අතර ඒවා මකන ලදි"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> හොඳින් ක්රියා නොකළේය, එය මකන ලදි ඇඟිලි සලකුණ මගින් ඔබේ දුරකථනය අගුලු හැරීමට එය නැවත සකසන්න."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> සහ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> හොඳින් ක්රියා නොකළේය, කාර්යසාධනය දියුණූ කිරීමට ඒවා මකන ලදි. ඔබේ ඇඟිලි සලකුණ මගින් ඔබේ දුරකථනය අගුලු හැරීමට ඒවා නැවත සකසන්න."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"මුහුණෙන් අගුලු හැරීම නැවත සකසන්න"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 46897cbbf7cb..9c2a87537a4b 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -203,6 +203,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Váš pracovný profil už v tomto zariadení nie je k dispozícii"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Príliš veľa pokusov o zadanie hesla"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Správca uvoľnil toto zariadenie na osobné používanie"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Súkromný priestor bol odstránený"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Vaša organizácia nepovoľuje súkromné priestory v tomto spravovanom zariadení."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Zariadenie je spravované"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Vaša organizácia spravuje toto zariadenie a môže sledovať sieťovú premávku. Klepnutím zobrazíte podrobnosti."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikácie majú prístup k vašej polohe"</string> @@ -285,8 +287,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Hlasový asistent"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Uzamknúť"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Odpovedať"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Nové upozornenie"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyzická klávesnica"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Zabezpečenie"</string> @@ -1191,8 +1192,8 @@ <string name="deleteText" msgid="4200807474529938112">"Odstrániť"</string> <string name="inputMethod" msgid="1784759500516314751">"Metóda vstupu"</string> <string name="editTextMenuTitle" msgid="857666911134482176">"Operácie s textom"</string> - <string name="error_handwriting_unsupported" msgid="7809438534946014050">"Ručné písanie nie je v tomto poli podporované"</string> - <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"V poliach pre heslá nie je ručné písanie podporované"</string> + <string name="error_handwriting_unsupported" msgid="7809438534946014050">"V tomto poli nie je rukopis podporovaný"</string> + <string name="error_handwriting_unsupported_password" msgid="5095401146106891087">"V poliach pre heslá nie je rukopis podporovaný"</string> <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Späť"</string> <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Prepnúť metódu vstupu"</string> <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Nedostatok ukladacieho priestoru"</string> @@ -2197,7 +2198,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Stlačiť tlačidlo doľava krížového ovládača"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Stlačiť tlačidlo doprava krížového ovládača"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Stlačiť stredné tlačidlo krížového ovládača"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Popis aplikácie <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Balík <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> bol vložený do kontajnera OBMEDZENÉ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"odoslal(a) obrázok"</string> @@ -2398,9 +2398,9 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Rozloženie klávesnice je nastavené na jazyky <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g> a <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Môžete to zmeniť klepnutím."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Fyzické klávesnice sú nakonfigurované"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Klávesnice si zobrazíte klepnutím"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Súkromný"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Súkromné"</string> <string name="profile_label_clone" msgid="769106052210954285">"Klon"</string> - <string name="profile_label_work" msgid="3495359133038584618">"Pracovný"</string> + <string name="profile_label_work" msgid="3495359133038584618">"Pracovné"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"2. pracovný"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"3. pracovný"</string> <string name="profile_label_test" msgid="9168641926186071947">"Testovací"</string> @@ -2418,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ako to funguje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Nespracovaná…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Znova nastavte odomknutie odtlačkom prsta"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Odtlačok <xliff:g id="FINGERPRINT">%s</xliff:g> nefungoval správne a bol odstránený"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Odtlačky <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nefungovali správne a boli odstránené"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Odtlačok <xliff:g id="FINGERPRINT">%s</xliff:g> nefungoval správne a bol odstránený. Ak chcete odomykať telefón odtlačkom prsta, nastavte ho znova."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Odtlačky <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nefungovali správne a boli odstránené. Ak chcete odomykať telefón odtlačkom prsta, nastavte ich znova."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Znova nastavte odomknutie tvárou"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 5c3696a89a22..b3530e716b2f 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -203,6 +203,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Vaš delovni profil ni več na voljo v tej napravi"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Preveč poskusov vnosa gesla"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Skrbnik je napravo prepustil osebni uporabi"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Zasebni prostor odstranjen"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Vaša organizacija ne dovoli zasebnih prostorov v tej upravljani napravi."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Naprava je upravljana"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Vaša organizacija upravlja to napravo in lahko nadzira omrežni promet. Dotaknite se za podrobnosti."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikacije imajo dostop do vaše lokacije"</string> @@ -2196,7 +2198,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Smerni gumb levo"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Smerni gumb desno"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Smerni gumb sredina"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Vrstica s podnapisi aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> je bil dodan v segment OMEJENO"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"je poslal(-a) sliko"</string> @@ -2417,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako deluje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"V teku …"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Vnovična nastavitev odklepanja s prstnim odtisom"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ni deloval pravilno in je bil izbrisan"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> in <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nista delovala pravilno in sta bila izbrisana"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ni deloval pravilno in je bil izbrisan. Znova ga nastavite, če želite telefon odklepati s prstnim odtisom."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> in <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nista delovala pravilno in sta bila izbrisana. Znova ju nastavite, če želite telefon odklepati s prstnim odtisom."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Vnovična nastavitev odklepanja z obrazom"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 4a6bdcb09b09..5f9a6425e208 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -201,6 +201,10 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profili yt i punës nuk është më i disponueshëm në këtë pajisje"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Shumë përpjekje për fjalëkalimin"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratori e refuzoi pajisjen për përdorim personal"</string> + <!-- no translation found for private_space_deleted_by_admin (1484365588862066939) --> + <skip /> + <!-- no translation found for private_space_deleted_by_admin_details (7007781735201818689) --> + <skip /> <string name="network_logging_notification_title" msgid="554983187553845004">"Pajisja është e menaxhuar"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organizata jote e menaxhon këtë pajisje dhe mund të monitorojë trafikun e rrjetit. Trokit për detaje."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikacionet mund të kenë qasje te vendndodhja jote"</string> @@ -283,8 +287,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ndihma zanore"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blloko"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Përgjigju"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Njoftim i ri"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastiera fizike"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Siguria"</string> @@ -2195,7 +2198,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Majtas në bllokun e drejtimit"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Djathtas në bllokun e drejtimit"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Qendra e bllokut të drejtimit"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Shiriti i nëntitullit të <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> është vendosur në grupin E KUFIZUAR"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"dërgoi një imazh"</string> @@ -2396,7 +2398,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Struktura e tastierës u caktua në: <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Trokit për ta ndryshuar."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Tastierat fizike u konfiguruan"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Trokit për të parë tastierat"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Privat"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Private"</string> <string name="profile_label_clone" msgid="769106052210954285">"Klon"</string> <string name="profile_label_work" msgid="3495359133038584618">"Puna"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Puna 2"</string> @@ -2416,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Si funksionon"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Në pritje..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfiguro përsëri \"Shkyçjen me gjurmën e gishtit\""</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> nuk po funksiononte mirë dhe u fshi"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> dhe <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nuk po funksiononin mirë dhe u fshinë"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nuk po funksiononte mirë dhe u fshi. Konfiguroje përsëri për ta shkyçur telefonin tënd me gjurmën e gishtit."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> dhe <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nuk po funksiononin mirë dhe u fshinë. Konfiguroji përsëri për ta shkyçur telefonin tënd me gjurmën e gishtit."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfiguro \"Shkyçjen me fytyrë\" përsëri"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 86387c95f727..301fe244bbf1 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -202,6 +202,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Пословни профил више није доступан на овом уређају"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Превише покушаја уноса лозинке"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Администратор је уступио уређај за личну употребу"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Приватан простор је уклоњен"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Организација не дозвољава приватне просторе на овом управљаном уређају."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Уређајем се управља"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Организација управља овим уређајем и може да надгледа мрежни саобраћај. Додирните за детаље."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Апликације могу да приступају вашој локацији"</string> @@ -284,8 +286,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Гласовна помоћ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Закључавање"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Одговори"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Ново обавештење"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физичка тастатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Безбедност"</string> @@ -2196,7 +2197,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"налево на D-pad-у"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"надесно на D-pad-у"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"центар на D-pad-у"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Трака са насловима апликације <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакет <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> је додат у сегмент ОГРАНИЧЕНО"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"је послао/ла слику"</string> @@ -2417,10 +2417,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Принцип рада"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"На чекању..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Поново подесите откључавање отиском прста"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> није функционисао и избрисали смо га"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> и <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> нису функционисали и избрисали смо их"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> није функционисао и избрисали смо га. Поново га подесите да бисте телефон откључавали отиском прста."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> и <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> нису функционисали и избрисали смо их. Поново их подесите да бисте телефон откључавали отиском прста."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Поново подесите откључавање лицем"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 6c15ad8ff76f..b3c2063a6df7 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Jobbprofilen är inte längre tillgänglig på enheten"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"För många försök med lösenord"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratören tillåter inte längre privat bruk av enheten"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Privat område har tagits bort"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Din organisation tillåter inte privata områden på den här hanterade enheten."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Enheten hanteras"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Organisationen hanterar den här enheten och kan övervaka nätverkstrafiken. Tryck om du vill veta mer."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Appar har åtkomst till din plats"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Låsning"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Svara"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Ny avisering"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysiskt tangentbord"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Säkerhet"</string> @@ -2195,14 +2196,13 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Styrkors, vänster"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Styrkors, höger"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Styrkors, mitten"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Textningsfält för <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> har placerats i hinken RESTRICTED"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"har skickat en bild"</string> <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Konversation"</string> <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Gruppkonversation"</string> <string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string> - <string name="resolver_personal_tab" msgid="2051260504014442073">"Privat"</string> + <string name="resolver_personal_tab" msgid="2051260504014442073">"Personlig"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"Jobb"</string> <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Personlig vy"</string> <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Jobbvy"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Så fungerar det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Väntar …"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfigurera fingeravtryckslås igen"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> fungerade inte bra och har raderats"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> och <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> fungerade inte bra och har raderats"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> fungerade inte bra och har raderats. Konfigurera det igen för att låsa upp telefonen med fingeravtryck."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> och <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> fungerade inte bra och har raderats. Konfigurera dem igen för att låsa upp telefonen med fingeravtryck."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfigurera ansiktslås igen"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index e8ba087fdcc6..f15796e5241b 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Wasifu wako wa kazini haupatikani tena kwenye kifaa hiki"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Umejaribu kuweka nenosiri mara nyingi mno"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Msimamizi aliacha kutumia kifaa kwa matumizi ya binafsi"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Sehemu ya faragha imeondolewa"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Shirika lako haliruhusu sehemu za faragha kwenye kifaa hiki kinachodhibitiwa."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Kifaa kinadhibitiwa"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Shirika lako linadhibiti kifaa hiki na huenda likafuatilia shughuli kwenye mtandao. Gusa ili upate maelezo zaidi."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Programu zinaweza kutambua mahali ulipo"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Kitufe cha kushoto cha Dpad"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Kitufe cha kulia cha Dpad"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Kitufe cha katikati cha Dpad"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Upau wa manukuu wa <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> kimewekwa katika kikundi KILICHODHIBITIWA"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"alituma picha"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Utaratibu wake"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Inashughulikiwa..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Weka tena mipangilio ya Kufungua kwa Alama ya Kidole"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Alama ya <xliff:g id="FINGERPRINT">%s</xliff:g> ilikuwa na hitilafu na imefutwa"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Alama za <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> na <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> zilikuwa na hitilafu na zimefutwa"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Alama ya <xliff:g id="FINGERPRINT">%s</xliff:g> ilikuwa na hitilafu na imefutwa. Iweke tena ili ufungue simu yako kwa alama ya kidole."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Alama za <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> na <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> zilikuwa na hitilafu na zimefutwa. Ziweke tena ili ufungue simu yako kwa alama ya kidole."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Weka tena mipangilio ya Kufungua kwa Uso"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 231b14c6a94d..f615965b721b 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"இந்தச் சாதனத்தில் இனி பணிக் கணக்கு கிடைக்காது"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"கடவுச்சொல்லை அதிக முறை தவறாக முயற்சித்துவிட்டீர்கள்"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"நிர்வாகியால் தனிப்பட்ட உபயோகத்திற்காக ஒதுக்கப்பட்ட சாதனம்"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"ரகசிய இடம் அகற்றப்பட்டது"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"இந்த நிர்வகிக்கப்படும் சாதனத்தில் ரகசிய இடங்களை உங்கள் நிறுவனம் அனுமதிப்பதில்லை."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"சாதனம் நிர்வகிக்கப்படுகிறது"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"உங்கள் நிறுவனம் இந்தச் சாதனத்தை நிர்வகிக்கும், அத்துடன் அது நெட்வொர்க் ட்ராஃபிக்கைக் கண்காணிக்கலாம். விவரங்களுக்கு, தட்டவும்."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ஆப்ஸ் உங்கள் இருப்பிடத்தை அணுக முடியும்"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"குரல் உதவி"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"பூட்டு"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"பதிலளி"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"புதிய அறிவிப்பு"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"கைமுறை கீபோர்டு"</string> <string name="notification_channel_security" msgid="8516754650348238057">"பாதுகாப்பு"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"இடது திசை காட்டும் பட்டன்"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"வலது திசை காட்டும் பட்டன்"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"மையப் பகுதியைக் காட்டும் பட்டன்"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸின் தலைப்புப் பட்டி."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> என்பதை வரம்பிடப்பட்ட பக்கெட்திற்குள் சேர்க்கப்பட்டது"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"படம் அனுப்பப்பட்டது"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 5517dbf47ff2..a84f6eb13d7e 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ఈ పరికరంలో మీ కార్యాలయ ప్రొఫైల్ ఇప్పుడు అందుబాటులో లేదు"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"చాలా ఎక్కువ పాస్వర్డ్ ప్రయత్నాలు చేశారు"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"వ్యక్తిగత వినియోగం కోసం నిర్వాహకులు పరికరాన్ని తీసి వేశారు"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"ప్రైవేట్ స్పేస్ తీసివేయబడింది"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"మీ సంస్థ ఈ మేనేజ్ చేసే పరికరంలో ప్రైవేట్ స్పేస్లను అనుమతించదు."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"పరికరం నిర్వహించబడింది"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"మీ సంస్థ ఈ పరికరాన్ని నిర్వహిస్తుంది మరియు నెట్వర్క్ ట్రాఫిక్ని పర్యవేక్షించవచ్చు. వివరాల కోసం నొక్కండి."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"యాప్లు మీ లొకేషన్ను యాక్సెస్ చేయగలవు"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ఎడమవైపున"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad కుడివైపున"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"DPad మధ్యన"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> క్యాప్షన్ బార్."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> పరిమితం చేయబడిన బకెట్లో ఉంచబడింది"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ఇమేజ్ను పంపారు"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ఇది ఎలా పని చేస్తుంది"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"పెండింగ్లో ఉంది..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"వేలిముద్ర అన్లాక్ను మళ్లీ సెటప్ చేయండి"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> సరిగ్గా పని చేయడం లేదు, తొలగించబడింది"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>, <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> బాగా పని చేయడం లేదు, తొలగించబడ్డాయి"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> సరిగ్గా పని చేయడం లేదు, తొలగించబడింది. వేలిముద్రతో మీ ఫోన్ను అన్లాక్ చేయడానికి దాన్ని మళ్లీ సెటప్ చేయండి."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>, <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> బాగా పని చేయడం లేదు, తొలగించబడ్డాయి. మీ వేలిముద్రతో మీ ఫోన్ను అన్లాక్ చేయడానికి వాటిని మళ్లీ సెటప్ చేయండి."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ఫేస్ అన్లాక్ను మళ్లీ సెటప్ చేయండి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 070875fcb4ea..e34b526ba940 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"โปรไฟล์งานของคุณไม่สามารถใช้ในอุปกรณ์นี้อีกต่อไป"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"พยายามป้อนรหัสผ่านหลายครั้งเกินไป"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"ผู้ดูแลระบบปล่อยอุปกรณ์ให้คุณใช้งานส่วนตัว"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"นำพื้นที่ส่วนตัวออกแล้ว"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"องค์กรของคุณไม่อนุญาตให้มีพื้นที่ส่วนตัวในอุปกรณ์ที่มีการจัดการเครื่องนี้"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"อุปกรณ์มีการจัดการ"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"องค์กรของคุณจัดการอุปกรณ์นี้และอาจตรวจสอบการจราจรของข้อมูลในเครือข่าย แตะเพื่อดูรายละเอียด"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"แอปจะเข้าถึงตำแหน่งของคุณได้"</string> @@ -2194,14 +2196,13 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad ซ้าย"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad ขวา"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad กึ่งกลาง"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"แถบคำบรรยาย <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"ใส่ <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ในที่เก็บข้อมูลที่ถูกจำกัดแล้ว"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ส่งรูปภาพ"</string> <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"การสนทนา"</string> <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"บทสนทนากลุ่ม"</string> <string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string> - <string name="resolver_personal_tab" msgid="2051260504014442073">"ส่วนตัว"</string> + <string name="resolver_personal_tab" msgid="2051260504014442073">"ส่วนบุคคล"</string> <string name="resolver_work_tab" msgid="2690019516263167035">"งาน"</string> <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"มุมมองส่วนตัว"</string> <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"ดูงาน"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"วิธีการทำงาน"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"รอดำเนินการ..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ตั้งค่าการปลดล็อกด้วยลายนิ้วมืออีกครั้ง"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> ทำงานได้ไม่ดีและถูกลบออกไปแล้ว"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> และ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ทำงานได้ไม่ดีและถูกลบออกไปแล้ว"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g>ทำงานได้ไม่ดีและถูกลบออกไปแล้ว ตั้งค่าอีกครั้งเพื่อปลดล็อกโทรศัพท์ด้วยลายนิ้วมือ"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> และ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ทำงานได้ไม่ดีและถูกลบออกไปแล้ว ตั้งค่าอีกครั้งเพื่อปลดล็อกโทรศัพท์ด้วยลายนิ้วมือ"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"ตั้งค่าการปลดล็อกด้วยใบหน้าอีกครั้ง"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 45a66a3b8516..4288460e6e97 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Hindi na available sa device na ito ang iyong profile sa trabaho"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Masyadong maraming pagsubok sa password"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Inalis ng admin ang device para sa personal na paggamit"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Naalis ang pribadong space"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Hindi pinapayagan ng iyong organisasyon ang mga pribadong space sa pinapamahalaang device na ito."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Pinamamahalaan ang device"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Pinamamahalaan ng iyong organisasyon ang device na ito, at maaari nitong subaybayan ang trapiko sa network. I-tap para sa mga detalye."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Maa-access ng mga app ang iyong lokasyon"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Left"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Right"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Center"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar ng <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Inilagay ang <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> sa PINAGHIHIGPITANG bucket"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"nagpadala ng larawan"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Paano ito gumagana"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Nakabinbin..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"I-set up ulit ang Pag-unlock Gamit ang Fingerprint"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Hindi gumagana nang maayos ang <xliff:g id="FINGERPRINT">%s</xliff:g> at na-delete ito"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Hindi gumagana nang maayos ang <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> at <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> at na-delete ang mga ito"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Hindi gumagana nang maayos ang <xliff:g id="FINGERPRINT">%s</xliff:g> at na-delete na ito. I-set up ulit ito para ma-unlock ang iyong telepono sa pamamagitan ng fingerprint."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Hindi gumagana nang maayos ang <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> at <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> at na-delete na ang mga ito. I-set up ulit ang mga ito para ma-unlock ang iyong telepono gamit ang fingerprint mo."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"I-set up ulit ang Pag-unlock Gamit ang Mukha"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 64498b524b24..906ccbc2ddc7 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"İş profiliniz arık bu cihazda kullanılamıyor"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Çok fazla şifre denemesi yapıldı"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Yönetici, cihazı kişisel kullanım için serbest bıraktı"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Özel alan kaldırıldı"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Kuruluşunuz bu yönetilen cihazda özel alan kullanılmasına izin vermiyor."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Cihaz yönetiliyor"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Kuruluşunuz bu cihazı yönetmekte olup ağ trafiğini izleyebilir. Ayrıntılar için dokunun."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Uygulamalar konumunuza erişebilir"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Sesli Yardım"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Tam kilitleme"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Yanıtla"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Yeni bildirim"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziksel klavye"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Güvenlik"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Sol"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Sağ"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Orta"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasının başlık çubuğu."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> KISITLANMIŞ gruba yerleştirildi"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"bir resim gönderildi"</string> @@ -2396,7 +2396,7 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Klavye düzeni <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g> olarak ayarlandı… Değiştirmek için dokunun."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Fiziksel klavyeler yapılandırıldı"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Klavyeleri görüntülemek için dokunun"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Gizli"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Özel"</string> <string name="profile_label_clone" msgid="769106052210954285">"Klon"</string> <string name="profile_label_work" msgid="3495359133038584618">"İş"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"İş 2"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"İşleyiş şekli"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Bekliyor..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Parmak İzi Kilidi\'ni tekrar kurun"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> iyi çalışmadığı için silindi"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ve <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> iyi çalışmadığı için silindi"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> iyi çalışmadığı için silindi. Telefonunuzun kilidini parmak iziyle açmak için tekrar kurun."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ve <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> iyi çalışmadığı için silindi. Telefonunuzun kilidini parmak izinizle açmak için tekrar kurun."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Yüz Tanıma Kilidi\'ni tekrar kurun"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index fcea7bb43e01..cc9df2e09efc 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -203,6 +203,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Робочий профіль більше не доступний на цьому пристрої"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Забагато спроб ввести пароль"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Адміністратор не дозволив використовувати пристрій для особистих потреб"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Приватний простір видалено"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Ваша організація не дозволяє мати приватні простори на цьому керованому пристрої."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Пристрій контролюється"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Адміністратор вашої організації контролює цей пристрій і відстежує мережевий трафік. Торкніться, щоб дізнатися більше."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Додаток має доступ до геоданих"</string> @@ -285,8 +287,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Голос. підказки"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Блокування"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Відповісти"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Нове сповіщення"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Фізична клавіатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Безпека"</string> @@ -2197,7 +2198,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Кнопка \"вліво\" панелі керування"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Кнопка \"вправо\" панелі керування"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Центральна кнопка панелі керування"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Смуга із субтитрами для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакет \"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>\" додано в сегмент з обмеженнями"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"надіслано зображення"</string> @@ -2418,10 +2418,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як це працює"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Обробка…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Налаштуйте розблокування відбитком пальця повторно"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"Відбиток \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" працював неналежним чином, і його видалено"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"Відбитки \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" і \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" працювали неналежним чином, і їх видалено"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Відбиток \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" працював неналежним чином, і його видалено. Налаштуйте його ще раз, щоб розблоковувати телефон за допомогою відбитка пальця."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Відбитки \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" і \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" працювали неналежним чином, і їх видалено. Налаштуйте їх ще раз, щоб розблоковувати телефон за допомогою відбитка пальця."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Налаштуйте фейс-контроль повторно"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index da592a4812f0..c0be641c84c7 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"آپ کا دفتری پروفائل اس آلہ پر مزید دستیاب نہیں ہے"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"پاس ورڈ کی بہت ساری کوششیں"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"منتظم نے ذاتی استعمال کے لیے آلہ کو دستبردار کیا ہے"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"پرائیویٹ اسپیس کو ہٹا دیا گیا"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"آپ کی تنظیم اس زیر انتظام آلے پر پرائیویٹ اسپیسز کو اجازت نہیں دیتی ہے۔"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"آلہ زیر انتظام ہے"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"آپ کی تنظیم اس آلے کا نظم کرتی ہے اور وہ نیٹ ورک ٹریفک کی نگرانی کر سکتی ہے۔ تفاصیل کیلئے تھپتھپائیں۔"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"ایپس آپ کے مقام تک رسائی حاصل کر سکتی ہیں"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad بائیں کریں"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad دائیں کریں"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad سینٹر"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> کی کیپشن بار۔"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> کو پابند کردہ بکٹ میں رکھ دیا گیا ہے"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"ایک تصویر بھیجی"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"اس کے کام کرنے کا طریقہ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"زیر التواء..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"فنگر پرنٹ اَن لاک کو دوبارہ سیٹ اپ کریں"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> اچھی طرح کام نہیں کر رہا تھا اور حذف کر دیا گیا تھا"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> اور <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> اچھی طرح کام نہیں کر رہے تھے اور انہیں حذف کر دیا گیا تھا"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> اچھی طرح کام نہیں کر رہا تھا اور حذف کر دیا گیا تھا۔ اپنے فون کو فنگر پرنٹ سے غیر مقفل کرنے کے لیے، اسے دوبارہ سیٹ اپ کریں۔"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> اور <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> اچھی طرح کام نہیں کر رہے تھے اور انہیں حذف کر دیا گیا تھا۔ اپنے فون کو اپنے فنگر پرنٹ سے غیر مقفل کرنے کے لیے انہیں دوبارہ سیٹ اپ کریں۔"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"فیس اَن لاک کو دوبارہ سیٹ اپ کریں"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 0ef4ddc4cc0b..517176b33a02 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Bu qurilmada endi ishchi profilingiz mavjud emas"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Parol ko‘p marta xato kiritildi"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator shaxsiy foydalanishga qoldirilgan qurilmani rad etdi"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Maxfiy makon olib tashlandi"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Tashkilotingiz mazkur boshqaruvdagi qurilmada maxfiy makon ochishni taqiqlagan."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Bu – boshqariladigan qurilma"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Tashkilotingiz bu qurilmani boshqaradi va tarmoq trafigini nazorat qilishi mumkin. Tafsilotlar uchun bosing."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Ilovalar joylashuv axborotidan foydalana oladi"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ovozli yordam"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Qulflash"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Javob berish"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Yangi bildirishnoma"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tashqi klaviatura"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Xavfsizlik"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad – chapga"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad – oʻngga"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad – markazga"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> taglavhalar paneli."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> cheklangan turkumga joylandi"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"rasm yuborildi"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ishlash tartibi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Kutilmoqda..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Barmoq izi bilan ochish funksiyasini qayta sozlang"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> yaxshi ishlamadi va oʻchirib tashlandi."</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> va <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> yaxshi ishlamadi va oʻchirib tashlandi."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> yaxshi ishlamadi va oʻchirib tashlandi. Telefonni barmoq izi bilan ochish uchun uni qayta sozlang."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> va <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> yaxshi ishlamadi va oʻchirib tashlandi. Telefonni barmoq izi bilan ochish uchun ularni qayta sozlang."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Yuz bilan ochishni qayta sozlash"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 4f2a9fd89682..e6db9dd4c680 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Hồ sơ công việc của bạn không có sẵn trên thiết bị này nữa"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Quá nhiều lần nhập mật khẩu"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Quản trị viên đã từ bỏ quyền sở hữu thiết bị để cho phép dùng vào mục đích cá nhân"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Đã xoá không gian riêng tư"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Tổ chức của bạn không cho phép tạo không gian riêng tư trên thiết bị được quản lý này."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Thiết bị được quản lý"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Tổ chức của bạn sẽ quản lý thiết bị này và có thể theo dõi lưu lượng truy cập mạng. Nhấn để biết chi tiết."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Ứng dụng có thể truy cập vào thông tin vị trí của bạn"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Chuyển sang trái bằng bàn phím di chuyển"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Chuyển sang phải bằng bàn phím di chuyển"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Căn giữa bằng bàn phím di chuyển"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Thanh phụ đề của <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Đã đưa <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> vào bộ chứa BỊ HẠN CHẾ"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"đã gửi hình ảnh"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cách hoạt động"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Đang chờ xử lý..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Thiết lập lại tính năng Mở khoá bằng vân tay"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"<xliff:g id="FINGERPRINT">%s</xliff:g> không dùng được và đã bị xoá"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> và <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> không dùng được và đã bị xoá"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> không dùng được và đã bị xoá. Hãy thiết lập lại để mở khoá điện thoại bằng vân tay."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> và <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> không dùng được và đã bị xoá. Hãy thiết lập lại để mở khoá điện thoại bằng vân tay."</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Thiết lập lại tính năng Mở khoá bằng khuôn mặt"</string> 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-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 10243220d600..4b0494004dd7 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"您的工作资料已不在此设备上"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"密码尝试次数过多"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"管理员已将该设备开放给个人使用"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"私密空间已移除"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"贵组织不允许在此受管设备上使用私密空间。"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"设备为受管理设备"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"贵单位会管理该设备,且可能会监控网络流量。点按即可了解详情。"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"应用可以访问您的位置信息"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"语音助理"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"锁定"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"回复"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"实体键盘"</string> <string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"向左方向键"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"向右方向键"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"方向键中心"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>的标题栏。"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> 已被放入受限存储分区"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"发送了一张图片"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"运作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待归档…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"重新设置指纹解锁功能"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"“<xliff:g id="FINGERPRINT">%s</xliff:g>”无法正常使用,已被删除"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"“<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>”和“<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>”无法正常使用,已被删除"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"“<xliff:g id="FINGERPRINT">%s</xliff:g>”无法正常使用,系统已将其删除。如要通过指纹解锁功能来解锁手机,请重新设置。"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"“<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>”和“<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>”无法正常使用,系统已将它们删除。如要通过指纹解锁功能来解锁手机,请重新设置。"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"重新设置“人脸解锁”功能"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index baf9ef574493..1aa9c72c8cb9 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"你的工作設定檔無法再在此裝置上使用"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"密碼輸入錯誤的次數過多"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"管理員已開放裝置供個人使用"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"已移除私人空間"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"你的機構不允許在此受管理的裝置上建立私人空間。"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"裝置已受管理"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"你的機構會管理此裝置,並可能會監控網絡流量。輕按即可瞭解詳情。"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"應用程式可存取你的位置"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"十字鍵向左鍵"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"十字鍵向右鍵"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"十字鍵中心鍵"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」的說明列。"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> 已納入受限制的儲存區"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"已傳送圖片"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待處理…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"重新設定「指紋解鎖」功能"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"由於「<xliff:g id="FINGERPRINT">%s</xliff:g>」無法正常運作,因此系統已將其刪除"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"由於「<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>」和「<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>」無法正常運作,因此系統已將其刪除。"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"由於「<xliff:g id="FINGERPRINT">%s</xliff:g>」無法正常運作,因此系統已將其刪除。請重新設定,才能使用指紋解鎖手機。"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"由於「<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>」和「<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>」無法正常運作,因此系統已將其刪除。請重新設定,才能使用指紋解鎖手機。"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"重新設定「面孔解鎖」功能"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 9eeb6dc70a28..1c83ee6d7465 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"你的工作資料夾已不在這個裝置上"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"密碼輸入錯誤的次數過多"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"管理員將這部裝置開放給個人使用"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"私人空間已移除"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"貴機構不允許在這部受管理的裝置上建立私人空間。"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"裝置受到管理"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"貴機構會管理這個裝置,且可能監控網路流量。輕觸即可瞭解詳情。"</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"應用程式可存取你的位置資訊"</string> @@ -2194,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad 向左移"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad 向右移"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad 置中"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」的說明文字列。"</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"已將「<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>」移入受限制的值區"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"傳送了一張圖片"</string> @@ -2415,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待處理…"</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"重新設定指紋解鎖"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"「<xliff:g id="FINGERPRINT">%s</xliff:g>」無法正常運作,系統已將其刪除"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"「<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>」和「<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>」無法正常運作,系統已將其刪除"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"「<xliff:g id="FINGERPRINT">%s</xliff:g>」無法正常運作,因此系統已將其刪除。請重新設定,才能用指紋解鎖手機。"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"「<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>」和「<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>」無法正常運作,因此系統已將其刪除。請重新設定,才能用指紋解鎖手機。"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"重新設定人臉解鎖"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 7749b1b6f04c..23754425047b 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -201,6 +201,8 @@ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Iphrofayela yakho yomsebenzi ayisatholakali kule divayisi"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Imizamo yamaphasiwedi eminingi kakhulu"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Umphathi udedela idivayisi ngokusetshenziswa komuntu siqu"</string> + <string name="private_space_deleted_by_admin" msgid="1484365588862066939">"Indawo engasese isusiwe"</string> + <string name="private_space_deleted_by_admin_details" msgid="7007781735201818689">"Inhlangano yakho ayivumeli izindawo zangasese kule divayisi ephethwe."</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Idivayisi iphethwe"</string> <string name="network_logging_notification_text" msgid="1327373071132562512">"Inhlangano yakho iphethe le divayisi futhi kungenzeka ingaqaphi ithrafikhi yenethiwekhi. Thephela imininingwane."</string> <string name="location_changed_notification_title" msgid="3620158742816699316">"Izinhlelo zokusebenza zingakwazi ukufinyelela endaweni yakho"</string> @@ -283,8 +285,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Isisekeli sezwi"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Khiya"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> - <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> - <skip /> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Phendula"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"Isaziso esisha"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Ikhibhodi ephathekayo"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Ukuphepha"</string> @@ -2195,7 +2196,6 @@ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Ngakwesokunxele se-Dpad"</string> <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Ngakwesokudla se-Dpad"</string> <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Isikhungo se-Dpad"</string> - <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Ibha yamazwibela we-<xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"I-<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ifakwe kubhakede LOKUKHAWULELWE"</string> <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string> <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"uthumele isithombe"</string> @@ -2416,10 +2416,8 @@ <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Indlela esebenza ngayo"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ilindile..."</string> <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Setha Ukuvula ngesigxivizo somunwe futhi"</string> - <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> - <skip /> - <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> - <skip /> + <string name="fingerprint_dangling_notification_msg_1" msgid="8517140433796229725">"I-<xliff:g id="FINGERPRINT">%s</xliff:g> ibingasebenzi kahle futhi isuliwe"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7578829498452127613">"I-<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> kanye ne-<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ibingasebenzi kahle futhi isuliwe"</string> <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"I-<xliff:g id="FINGERPRINT">%s</xliff:g> ibingasebenzi kahle futhi isuliwe. Phinde uyisethe ukuze uvule ifoni yakho ngesigxivizo somunwe."</string> <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"I-<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> kanye ne-<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ibingasebenzi kahle futhi isuliwe. Phinde uyisethe ukuze uvule ifoni yakho ngesigxivizo somunwe wakho"</string> <string name="face_dangling_notification_title" msgid="947852541060975473">"Setha Ukuvula Ngobuso futhi"</string> diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 97e753e2bdeb..575573cb0ffb 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -136,11 +136,6 @@ <item>@color/search_url_text_material_light</item> </array> - <array name="preloaded_freeform_multi_window_drawables"> - <item>@drawable/decor_maximize_button_dark</item> - <item>@drawable/decor_maximize_button_light</item> - </array> - <!-- Used in LocalePicker --> <string-array translatable="false" name="special_locale_codes"> <!-- http://b/17150708 - ensure that the list of languages says "Arabic" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 0676f721c469..0706b32dfbb5 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 @@ -4199,13 +4200,6 @@ automatically try to pair with it when the device exits tablet mode. --> <string translatable="false" name="config_packagedKeyboardName"></string> - <!-- The device supports freeform window management. Windows have title bars and can be moved - and resized. If you set this to true, you also need to add - PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT feature to your device specification. - The duplication is necessary, because this information is used before the features are - available to the system.--> - <bool name="config_freeformWindowManagement">false</bool> - <!-- If set, this will force all windows to draw the status bar background, including the apps that have not requested doing so (via the WindowManager.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag). --> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index e420ffe68e4c..dcda5d8669a4 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,29 @@ <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" /> + + <!-- Boolean indicating whether Telephony should force PhoneGlobals creation + regardless of FEATURE_TELEPHONY presence. + --> + <bool name="config_force_phone_globals_creation">false</bool> + <java-symbol type="bool" name="config_force_phone_globals_creation" /> + </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/strings.xml b/core/res/res/values/strings.xml index a956a43b0441..87141c790f0c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5991,8 +5991,6 @@ <string name="accessibility_system_action_dpad_right_label">Dpad Right</string> <!-- Label for Dpad center action [CHAR LIMIT=NONE] --> <string name="accessibility_system_action_dpad_center_label">Dpad Center</string> - <!-- Accessibility description of caption view --> - <string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string> <!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] --> <string name="as_app_forced_to_restricted_bucket"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7e2c111719e5..bb73934450c9 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -402,7 +402,6 @@ <java-symbol type="bool" name="config_supportMicNearUltrasound" /> <java-symbol type="bool" name="config_supportSpeakerNearUltrasound" /> <java-symbol type="bool" name="config_supportAudioSourceUnprocessed" /> - <java-symbol type="bool" name="config_freeformWindowManagement" /> <java-symbol type="bool" name="config_supportsBubble" /> <java-symbol type="bool" name="config_supportsMultiWindow" /> <java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" /> @@ -1266,7 +1265,6 @@ <java-symbol type="array" name="networkAttributes" /> <java-symbol type="array" name="preloaded_color_state_lists" /> <java-symbol type="array" name="preloaded_drawables" /> - <java-symbol type="array" name="preloaded_freeform_multi_window_drawables" /> <java-symbol type="array" name="sim_colors" /> <java-symbol type="array" name="special_locale_codes" /> <java-symbol type="array" name="special_locale_names" /> @@ -1804,6 +1802,7 @@ <java-symbol type="dimen" name="taskbar_frame_height" /> <java-symbol type="dimen" name="status_bar_height" /> <java-symbol type="dimen" name="display_cutout_touchable_region_size" /> + <java-symbol type="dimen" name="system_gestures_distance_threshold" /> <java-symbol type="dimen" name="system_gestures_start_threshold" /> <java-symbol type="dimen" name="quick_qs_offset_height" /> <java-symbol type="drawable" name="ic_jog_dial_sound_off" /> @@ -1860,7 +1859,7 @@ <java-symbol type="integer" name="config_lidNavigationAccessibility" /> <java-symbol type="integer" name="config_lidOpenRotation" /> <java-symbol type="integer" name="config_longPressOnHomeBehavior" /> - <java-symbol type="integer" name="config_shortPressOnSettingsBehavior" /> + <java-symbol type="integer" name="config_settingsKeyBehavior" /> <java-symbol type="layout" name="global_actions" /> <java-symbol type="layout" name="global_actions_item" /> <java-symbol type="layout" name="global_actions_silent_mode" /> @@ -2469,16 +2468,6 @@ <!-- From Phone --> <java-symbol type="bool" name="config_built_in_sip_phone" /> - <java-symbol type="id" name="maximize_window" /> - <java-symbol type="id" name="close_window" /> - <java-symbol type="layout" name="decor_caption" /> - <java-symbol type="drawable" name="decor_caption_title_focused" /> - <java-symbol type="drawable" name="decor_close_button_dark" /> - <java-symbol type="drawable" name="decor_close_button_light" /> - <java-symbol type="drawable" name="decor_maximize_button_dark" /> - <java-symbol type="drawable" name="decor_maximize_button_light" /> - <java-symbol type="color" name="decor_button_dark_color" /> - <java-symbol type="color" name="decor_button_light_color" /> <java-symbol type="array" name="unloggable_phone_numbers" /> <!-- From TelephonyProvider --> @@ -4469,8 +4458,6 @@ <java-symbol type="string" name="accessibility_system_action_dpad_right_label" /> <java-symbol type="string" name="accessibility_system_action_dpad_center_label" /> - <java-symbol type="string" name="accessibility_freeform_caption" /> - <!-- For Wide Color Gamut --> <java-symbol type="bool" name="config_enableWcgMode" /> diff --git a/core/tests/coretests/OWNERS b/core/tests/coretests/OWNERS index b7e008b196ff..b669e3bc4f30 100644 --- a/core/tests/coretests/OWNERS +++ b/core/tests/coretests/OWNERS @@ -3,3 +3,4 @@ include platform/frameworks/base:/services/core/java/com/android/server/am/OWNER per-file BinderTest.java = file:platform/frameworks/native:/libs/binder/OWNERS per-file ParcelTest.java = file:platform/frameworks/native:/libs/binder/OWNERS per-file SurfaceControlRegistryTests.java = file:/services/core/java/com/android/server/wm/OWNERS +per-file VintfObjectTest.java = file:platform/system/libvintf:/OWNERS diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java index 89c2b3cecfef..b972882e68e6 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java @@ -128,7 +128,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance true, // ensureStatusBarContrastWhenTransparent true, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_RESIZEABLE, // resizeMode @@ -153,7 +154,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -169,7 +171,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x2222222, // colorBackground 0x3333332, // statusBarColor 0x4444442, // navigationBarColor - 0, // statusBarAppearance + 0x5555552, // systemBarsAppeareance + 0x6666662, // topOpaqueSystemBarsAppeareance true, // ensureStatusBarContrastWhenTransparent true, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_RESIZEABLE, // resizeMode @@ -200,7 +203,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -223,7 +227,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -256,6 +261,8 @@ public class ActivityManagerTest extends AndroidTestCase { assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor()); assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor()); assertEquals(td1.getSystemBarsAppearance(), td2.getSystemBarsAppearance()); + assertEquals(td1.getTopOpaqueSystemBarsAppearance(), + td2.getTopOpaqueSystemBarsAppearance()); assertEquals(td1.getResizeMode(), td2.getResizeMode()); assertEquals(td1.getMinWidth(), td2.getMinWidth()); assertEquals(td1.getMinHeight(), td2.getMinHeight()); 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/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java index 548b8ec3f6de..8071d3dff619 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java @@ -19,6 +19,7 @@ package android.database.sqlite; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -507,6 +508,12 @@ public class SQLiteRawStatementTest { s.bindInt(1, 3); s.step(); s.reset(); + // Bind a zero-length blob + s.clearBindings(); + s.bindInt(1, 4); + s.bindBlob(2, new byte[0]); + s.step(); + s.reset(); } mDatabase.setTransactionSuccessful(); } finally { @@ -545,6 +552,17 @@ public class SQLiteRawStatementTest { for (int i = 0; i < c.length; i++) c[i] = 0; s.bindInt(1, 3); assertTrue(s.step()); + assertNull(s.getColumnBlob(0)); + assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0)); + for (int i = 0; i < c.length; i++) assertEquals(0, c[i]); + s.reset(); + + // Fetch the zero-length blob + s.bindInt(1, 4); + assertTrue(s.step()); + byte[] r = s.getColumnBlob(0); + assertNotNull(r); + assertEquals(0, r.length); assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0)); for (int i = 0; i < c.length; i++) assertEquals(0, c[i]); s.reset(); @@ -572,6 +590,83 @@ public class SQLiteRawStatementTest { } @Test + public void testText() { + mDatabase.beginTransaction(); + try { + final String query = "CREATE TABLE t1 (i int, b text)"; + try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { + assertFalse(s.step()); + } + mDatabase.setTransactionSuccessful(); + } finally { + mDatabase.endTransaction(); + } + + // Insert data into the table. + mDatabase.beginTransaction(); + try { + final String query = "INSERT INTO t1 (i, b) VALUES (?1, ?2)"; + try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { + // Bind a string + s.bindInt(1, 1); + s.bindText(2, "text"); + s.step(); + s.reset(); + s.clearBindings(); + + // Bind a zero-length string + s.bindInt(1, 2); + s.bindText(2, ""); + s.step(); + s.reset(); + s.clearBindings(); + + // Bind a null string + s.clearBindings(); + s.bindInt(1, 3); + s.step(); + s.reset(); + s.clearBindings(); + } + mDatabase.setTransactionSuccessful(); + } finally { + mDatabase.endTransaction(); + } + + // Read back data and verify it against the reference copy. + mDatabase.beginTransactionReadOnly(); + try { + final String query = "SELECT (b) FROM t1 WHERE i = ?1"; + try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { + // Fetch the entire reference array. + s.bindInt(1, 1); + assertTrue(s.step()); + assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_TEXT, s.getColumnType(0)); + + String a = s.getColumnText(0); + assertNotNull(a); + assertEquals(a, "text"); + s.reset(); + + s.bindInt(1, 2); + assertTrue(s.step()); + String b = s.getColumnText(0); + assertNotNull(b); + assertEquals(b, ""); + s.reset(); + + s.bindInt(1, 3); + assertTrue(s.step()); + String c = s.getColumnText(0); + assertNull(c); + s.reset(); + } + } finally { + mDatabase.endTransaction(); + } + } + + @Test public void testParameterMetadata() { createComplexDatabase(); diff --git a/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java index 17980ac5e735..f8800cb0fea0 100644 --- a/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java +++ b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java @@ -18,6 +18,7 @@ package android.view; import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; +import static android.view.ContentRecordingSession.TASK_ID_UNKNOWN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -45,6 +46,7 @@ import org.junit.runner.RunWith; @Presubmit public class ContentRecordingSessionTest { private static final int DISPLAY_ID = 1; + private static final int TASK_ID = 123; private static final IBinder WINDOW_TOKEN = new Binder("DisplayContentWindowToken"); @Test @@ -65,6 +67,16 @@ public class ContentRecordingSessionTest { ContentRecordingSession session = ContentRecordingSession.createTaskSession(WINDOW_TOKEN); assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK); assertThat(session.getTokenToRecord()).isEqualTo(WINDOW_TOKEN); + assertThat(session.getTaskId()).isEqualTo(TASK_ID_UNKNOWN); + } + + @Test + public void testSecondaryTaskConstructor() { + ContentRecordingSession session = + ContentRecordingSession.createTaskSession(WINDOW_TOKEN, TASK_ID); + assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK); + assertThat(session.getTokenToRecord()).isEqualTo(WINDOW_TOKEN); + assertThat(session.getTaskId()).isEqualTo(TASK_ID); } @Test @@ -73,6 +85,7 @@ public class ContentRecordingSessionTest { DEFAULT_DISPLAY); assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY); assertThat(session.getTokenToRecord()).isNull(); + assertThat(session.getTaskId()).isEqualTo(TASK_ID_UNKNOWN); } @Test diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index 0b1b40c8ba8b..07446e7617aa 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -41,11 +41,13 @@ import android.app.Instrumentation; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.LargeTest; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.DisplayMetrics; import android.widget.FrameLayout; +import android.widget.ProgressBar; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; @@ -623,6 +625,162 @@ public class ViewFrameRateTest { assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory()); } + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void idleDetected() throws Throwable { + waitForFrameRateCategoryToSettle(); + mActivityRule.runOnUiThread(() -> { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH); + mMovingView.setFrameContentVelocity(Float.MAX_VALUE); + mMovingView.invalidate(); + runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH, + mViewRoot.getLastPreferredFrameRateCategory())); + }); + waitForAfterDraw(); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void vectorDrawableFrameRate() throws Throwable { + final ProgressBar[] progressBars = new ProgressBar[3]; + final ViewGroup[] parents = new ViewGroup[1]; + mActivityRule.runOnUiThread(() -> { + ViewGroup parent = (ViewGroup) mMovingView.getParent(); + parents[0] = parent; + ProgressBar progressBar1 = new ProgressBar(mActivity); + parent.addView(progressBar1); + progressBar1.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + progressBar1.setIndeterminate(true); + progressBars[0] = progressBar1; + + ProgressBar progressBar2 = new ProgressBar(mActivity); + parent.addView(progressBar2); + progressBar2.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL); + progressBar2.setIndeterminate(true); + progressBars[1] = progressBar2; + + ProgressBar progressBar3 = new ProgressBar(mActivity); + parent.addView(progressBar3); + progressBar3.setRequestedFrameRate(45f); + progressBar3.setIndeterminate(true); + progressBars[2] = progressBar3; + }); + waitForFrameRateCategoryToSettle(); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(45f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NORMAL, mViewRoot.getLastPreferredFrameRateCategory()); + + // Removing the vector drawable with NORMAL should drop the category to LOW + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[1])); + Thread.sleep(1000); + assertEquals(45f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + // Removing the one voting for frame rate should leave only the category + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[2])); + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + // Removing the last one should leave it with no preference + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[0])); + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void renderNodeAnimatorFrameRateCanceled() throws Throwable { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + waitForFrameRateCategoryToSettle(); + + RenderNodeAnimator[] renderNodeAnimator = new RenderNodeAnimator[1]; + renderNodeAnimator[0] = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, 0f); + renderNodeAnimator[0].setDuration(100000); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].setTarget(mMovingView); + renderNodeAnimator[0].start(); + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + runAfterDraw(() -> { + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + }); + }); + waitForAfterDraw(); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].cancel(); + }); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void renderNodeAnimatorFrameRateRemoved() throws Throwable { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + waitForFrameRateCategoryToSettle(); + + RenderNodeAnimator[] renderNodeAnimator = new RenderNodeAnimator[1]; + renderNodeAnimator[0] = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, 0f); + renderNodeAnimator[0].setDuration(100000); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].setTarget(mMovingView); + renderNodeAnimator[0].start(); + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + runAfterDraw(() -> { + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + }); + }); + waitForAfterDraw(); + + mActivityRule.runOnUiThread(() -> { + ViewGroup parent = (ViewGroup) mMovingView.getParent(); + assert parent != null; + parent.removeView(mMovingView); + }); + + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + private void runAfterDraw(@NonNull Runnable runnable) { Handler handler = new Handler(Looper.getMainLooper()); mAfterDrawLatch = new CountDownLatch(1); diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index a7f817665f23..94e187aed698 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -1287,6 +1287,31 @@ public class ViewRootImplTest { } @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY) + public void votePreferredFrameRate_velocityVotedAfterOnDraw() throws Throwable { + mView = new View(sContext); + double delta = 0.1; + float pixelsPerSecond = 1000_000; + float expectedFrameRate = 120; + attachViewToWindow(mView); + sInstrumentation.waitForIdleSync(); + ViewRootImpl viewRoot = mView.getViewRootImpl(); + waitForFrameRateCategoryToSettle(mView); + + sInstrumentation.runOnMainSync(() -> { + mView.setFrameContentVelocity(pixelsPerSecond); + mView.invalidate(); + assertEquals(0, viewRoot.getPreferredFrameRate(), delta); + assertEquals(0, viewRoot.getLastPreferredFrameRate(), delta); + runAfterDraw(() -> { + assertEquals(expectedFrameRate, viewRoot.getPreferredFrameRate(), delta); + assertEquals(expectedFrameRate, viewRoot.getLastPreferredFrameRate(), delta); + }); + }); + waitForAfterDraw(); + } + + @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); ShellIdentityUtils.invokeWithShellPermissions(() -> { 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/core.protolog.pb b/data/etc/core.protolog.pb Binary files differindex b41a607e91c7..a105ba756d51 100644 --- a/data/etc/core.protolog.pb +++ b/data/etc/core.protolog.pb diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 500e9b7d4213..db68f9528151 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2035,6 +2035,24 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/PhysicalDisplaySwitchTransitionLauncher.java" }, + "-1640401313436844534": { + "message": "Resetting frozen recents task list reason=app touch win=%s x=%d y=%d insetFrame=%s", + "level": "INFO", + "group": "WM_DEBUG_TASKS", + "at": "com\/android\/server\/wm\/RecentTasks.java" + }, + "-8803811426486764449": { + "message": "Setting frozen recents task list", + "level": "INFO", + "group": "WM_DEBUG_TASKS", + "at": "com\/android\/server\/wm\/RecentTasks.java" + }, + "4040735335719974079": { + "message": "Resetting frozen recents task list reason=timeout", + "level": "INFO", + "group": "WM_DEBUG_TASKS", + "at": "com\/android\/server\/wm\/RecentTasks.java" + }, "3308140128142966415": { "message": "remove RecentTask %s when finishing user %d", "level": "INFO", @@ -3223,6 +3241,36 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/TransitionController.java" }, + "-4546322749928357965": { + "message": "Registering transition player %s over %d other players", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, + "-4250307779892136611": { + "message": "Registering transition player %s ", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, + "3242771541905259983": { + "message": "Attempt to unregister transition player %s but it isn't registered", + "level": "WARN", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, + "3691912781236221027": { + "message": "Unregistering active transition player %s at index=%d leaving %d in stack", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, + "-2879980134100946679": { + "message": "Unregistering transition player %s at index=%d leaving %d in stack", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "-4235778637051052061": { "message": "Disabling player for transition #%d because display isn't enabled yet", "level": "VERBOSE", diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 0650b7817729..211f74a47bdd 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -16,6 +16,7 @@ package android.graphics; +import android.animation.Animator; import android.annotation.BytesLong; import android.annotation.ColorInt; import android.annotation.FloatRange; @@ -1639,7 +1640,7 @@ public final class RenderNode { */ public interface AnimationHost { /** @hide */ - void registerAnimatingRenderNode(RenderNode animator); + void registerAnimatingRenderNode(RenderNode renderNode, Animator animator); /** @hide */ void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator); @@ -1654,7 +1655,7 @@ public final class RenderNode { throw new IllegalStateException("Cannot start this animator on a detached view!"); } nAddAnimator(mNativeRenderNode, animator.getNativeAnimator()); - mAnimationHost.registerAnimatingRenderNode(this); + mAnimationHost.registerAnimatingRenderNode(this, animator); } /** @hide */ diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 55f205bb14a6..d4bb461c284e 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -1266,6 +1266,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private final IntArray mPendingAnimationActions = new IntArray(); private final AnimatedVectorDrawable mDrawable; private long mTotalDuration; + private AnimatorListener mThreadedRendererAnimatorListener; VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { mDrawable = drawable; @@ -1689,6 +1690,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationStart(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationStart(null); + } } // This should only be called after animator has been added to the RenderNode target. @@ -1717,6 +1721,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationStart(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationStart(null); + } } @Override @@ -1725,6 +1732,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } @Override + public void setThreadedRendererAnimatorListener(AnimatorListener animatorListener) { + mThreadedRendererAnimatorListener = animatorListener; + } + + @Override public boolean canReverse() { return mIsReversible; } @@ -1788,6 +1800,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationEnd(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationEnd(null); + } } // onFinished: should be called from native diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt deleted file mode 100644 index 0ac6265ecf27..000000000000 --- a/ktfmt_includes.txt +++ /dev/null @@ -1,740 +0,0 @@ -+services/permission -+packages/SystemUI --packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt --packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt --packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt --packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt --packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt --packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt --packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt --packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt --packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt --packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt --packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt --packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt --packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt --packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt --packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt --packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt --packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt --packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt --packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt --packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt --packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt --packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt --packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt --packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt --packages/SystemUI/src/com/android/keyguard/ClockEventController.kt --packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt --packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt --packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt --packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt --packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt --packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt --packages/SystemUI/src/com/android/systemui/BootCompleteCache.kt --packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt --packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt --packages/SystemUI/src/com/android/systemui/ChooserSelector.kt --packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt --packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt --packages/SystemUI/src/com/android/systemui/DualToneHandler.kt --packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt --packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt --packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt --packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt --packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt --packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt --packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt --packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt --packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt --packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt --packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt --packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt --packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt --packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt --packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt --packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt --packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt --packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt --packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt --packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt --packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt --packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt --packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt --packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt --packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt --packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt --packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt --packages/SystemUI/src/com/android/systemui/controls/TooltipManager.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt --packages/SystemUI/src/com/android/systemui/controls/controller/StatefulControlSubscriber.kt --packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt --packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsFeatureEnabled.kt --packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt --packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt --packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt --packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt --packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt --packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt --packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt --packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt --packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt --packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt --packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt --packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt --packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt --packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt --packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt --packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt --packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt --packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt --packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt --packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt --packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt --packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt --packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt --packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt --packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt --packages/SystemUI/src/com/android/systemui/dump/LogBufferFreezer.kt --packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt --packages/SystemUI/src/com/android/systemui/flags/Flags.kt --packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt --packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt --packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt --packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt --packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt --packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt --packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt --packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt --packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt --packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt --packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt --packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt --packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt --packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt --packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt --packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt --packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt --packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt --packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt --packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt --packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt --packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt --packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt --packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt --packages/SystemUI/src/com/android/systemui/privacy/MediaProjectionPrivacyItemMonitor.kt --packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt --packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt --packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt --packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt --packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt --packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt --packages/SystemUI/src/com/android/systemui/qs/QSExpansionPathInterpolator.kt --packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt --packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt --packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt --packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt --packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt --packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt --packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.kt --packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt --packages/SystemUI/src/com/android/systemui/qs/external/QSExternalModule.kt --packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt --packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt --packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt --packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt --packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt --packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt --packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt --packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt --packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt --packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt --packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt --packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt --packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt --packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt --packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt --packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt --packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt --packages/SystemUI/src/com/android/systemui/settings/UserContentResolverProvider.kt --packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt --packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt --packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt --packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt --packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt --packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt --packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt --packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt --packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt --packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt --packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt --packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt --packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt --packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt --packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt --packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt --packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt --packages/SystemUI/src/com/android/systemui/smartspace/SmartspacePrecondition.kt --packages/SystemUI/src/com/android/systemui/smartspace/SmartspaceTargetFilter.kt --packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt --packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt --packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenAndDreamTargetFilter.kt --packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt --packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt --packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt --packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt --packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt --packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt --packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt --packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt --packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt --packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt --packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt --packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt --packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt --packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt --packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileStatusTrackerFactory.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt --packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt --packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt --packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/FeedbackIcon.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTracker.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractor.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/RemoteInputViewModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarIconBlocklist.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyState.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletController.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt --packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt --packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt --packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt --packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt --packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt --packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt --packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt --packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt --packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt --packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt --packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt --packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt --packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt --packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt --packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt --packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt --packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt --packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt --packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt --packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt --packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt --packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt --packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt --packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt --packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt --packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt --packages/SystemUI/src/com/android/systemui/util/SafeMarqueeTextView.kt --packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt --packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt --packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt --packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt --packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt --packages/SystemUI/src/com/android/systemui/util/animation/MeasurementInput.kt --packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt --packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt --packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt --packages/SystemUI/src/com/android/systemui/util/concurrency/Execution.kt --packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt --packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt --packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt --packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt --packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt --packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt --packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt --packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt --packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt --packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt --packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt --packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt --packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt --packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt --packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt --packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt --packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt --packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt --packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt --packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt --packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt --packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt --packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt --packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt --packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt --packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt --packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt --packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt --packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt --packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt --packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt --packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt --packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt --packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt --packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt --packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt --packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt --packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt --packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 29936cc2cac3..774b2129497d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -200,6 +200,10 @@ class DividerPresenter implements View.OnTouchListener { } // At this point, a divider is required. + final TaskFragmentContainer primaryContainer = + topSplitContainer.getPrimaryContainer(); + final TaskFragmentContainer secondaryContainer = + topSplitContainer.getSecondaryContainer(); // Create the decor surface if one is not available yet. final SurfaceControl decorSurface = parentInfo.getDecorSurface(); @@ -207,41 +211,44 @@ class DividerPresenter implements View.OnTouchListener { // Clean up when the decor surface is currently unavailable. removeDivider(); // Request to create the decor surface - createOrMoveDecorSurfaceLocked(wct, topSplitContainer.getPrimaryContainer()); + createOrMoveDecorSurfaceLocked(wct, primaryContainer); return; } // Update the decor surface owner if needed. boolean isDraggableExpandType = SplitAttributesHelper.isDraggableExpandType(splitAttributes); - final TaskFragmentContainer decorSurfaceOwnerContainer = isDraggableExpandType - ? topSplitContainer.getSecondaryContainer() - : topSplitContainer.getPrimaryContainer(); + final TaskFragmentContainer decorSurfaceOwnerContainer = + isDraggableExpandType ? secondaryContainer : primaryContainer; if (!Objects.equals( mDecorSurfaceOwner, decorSurfaceOwnerContainer.getTaskFragmentToken())) { createOrMoveDecorSurfaceLocked(wct, decorSurfaceOwnerContainer); } - final boolean isVerticalSplit = isVerticalSplit(topSplitContainer); - final boolean isReversedLayout = isReversedLayout( - topSplitContainer.getCurrentSplitAttributes(), - parentInfo.getConfiguration()); + + final Configuration parentConfiguration = parentInfo.getConfiguration(); + final Rect taskBounds = parentConfiguration.windowConfiguration.getBounds(); + final boolean isVerticalSplit = isVerticalSplit(splitAttributes); + final boolean isReversedLayout = isReversedLayout(splitAttributes, parentConfiguration); + final int dividerWidthPx = getDividerWidthPx(dividerAttributes); updateProperties( new Properties( - parentInfo.getConfiguration(), + parentConfiguration, dividerAttributes, decorSurface, getInitialDividerPosition( - topSplitContainer, isVerticalSplit, isReversedLayout), + primaryContainer, secondaryContainer, taskBounds, + dividerWidthPx, isDraggableExpandType, isVerticalSplit, + isReversedLayout), isVerticalSplit, isReversedLayout, parentInfo.getDisplayId(), isDraggableExpandType, - getContainerBackgroundColor(topSplitContainer.getPrimaryContainer(), - DEFAULT_PRIMARY_VEIL_COLOR), - getContainerBackgroundColor(topSplitContainer.getSecondaryContainer(), - DEFAULT_SECONDARY_VEIL_COLOR) + getContainerBackgroundColor( + primaryContainer, DEFAULT_PRIMARY_VEIL_COLOR), + getContainerBackgroundColor( + secondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR) )); } } @@ -338,32 +345,31 @@ class DividerPresenter implements View.OnTouchListener { @VisibleForTesting static int getInitialDividerPosition( - @NonNull SplitContainer splitContainer, + @NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer, + @NonNull Rect taskBounds, + int dividerWidthPx, + boolean isDraggableExpandType, boolean isVerticalSplit, boolean isReversedLayout) { - final Rect primaryBounds = - splitContainer.getPrimaryContainer().getLastRequestedBounds(); - final Rect secondaryBounds = - splitContainer.getSecondaryContainer().getLastRequestedBounds(); - final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes(); - - if (SplitAttributesHelper.isDraggableExpandType(splitAttributes)) { - // If the container is fully expanded by dragging the divider, we display the divider - // on the edge. - final int dividerWidth = getDividerWidthPx(splitAttributes.getDividerAttributes()); + if (isDraggableExpandType) { + // If the secondary container is fully expanded by dragging the divider, we display the + // divider on the edge. final int fullyExpandedPosition = isVerticalSplit - ? primaryBounds.right - dividerWidth - : primaryBounds.bottom - dividerWidth; + ? taskBounds.width() - dividerWidthPx + : taskBounds.height() - dividerWidthPx; return isReversedLayout ? fullyExpandedPosition : 0; } else { + final Rect primaryBounds = primaryContainer.getLastRequestedBounds(); + final Rect secondaryBounds = secondaryContainer.getLastRequestedBounds(); return isVerticalSplit ? Math.min(primaryBounds.right, secondaryBounds.right) : Math.min(primaryBounds.bottom, secondaryBounds.bottom); } } - private static boolean isVerticalSplit(@NonNull SplitContainer splitContainer) { - final int layoutDirection = splitContainer.getCurrentSplitAttributes().getLayoutDirection(); + private static boolean isVerticalSplit(@NonNull SplitAttributes splitAttributes) { + final int layoutDirection = splitAttributes.getLayoutDirection(); switch (layoutDirection) { case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: @@ -510,11 +516,16 @@ class DividerPresenter implements View.OnTouchListener { if (mProperties != null && mRenderer != null) { final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); mDividerPosition = calculateDividerPosition( - event, taskBounds, mRenderer.mDividerWidthPx, + event, taskBounds, mProperties.mDividerWidthPx, mProperties.mDividerAttributes, mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition()); mRenderer.setDividerPosition(mDividerPosition); - switch (event.getAction()) { + + // Convert to use screen-based coordinates to prevent lost track of motion events + // while moving divider bar and calculating dragging velocity. + event.setLocation(event.getRawX(), event.getRawY()); + final int action = event.getAction() & MotionEvent.ACTION_MASK; + switch (action) { case MotionEvent.ACTION_DOWN: onStartDragging(event); break; @@ -671,8 +682,8 @@ class DividerPresenter implements View.OnTouchListener { final int minPosition = calculateMinPosition(); final int maxPosition = calculateMaxPosition(); final int fullyExpandedPosition = mProperties.mIsVerticalSplit - ? taskBounds.right - mRenderer.mDividerWidthPx - : taskBounds.bottom - mRenderer.mDividerWidthPx; + ? taskBounds.width() - mProperties.mDividerWidthPx + : taskBounds.height() - mProperties.mDividerWidthPx; if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) { final float displayDensity = getDisplayDensity(); @@ -713,9 +724,9 @@ class DividerPresenter implements View.OnTouchListener { return snap(dividerPosition, possiblePositions); } if (velocity < 0) { - return 0; + return minPosition; } else { - return fullyExpandedPosition; + return maxPosition; } } @@ -777,7 +788,7 @@ class DividerPresenter implements View.OnTouchListener { private int calculateMinPosition() { return calculateMinPosition( mProperties.mConfiguration.windowConfiguration.getBounds(), - mRenderer.mDividerWidthPx, mProperties.mDividerAttributes, + mProperties.mDividerWidthPx, mProperties.mDividerAttributes, mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout); } @@ -785,7 +796,7 @@ class DividerPresenter implements View.OnTouchListener { private int calculateMaxPosition() { return calculateMaxPosition( mProperties.mConfiguration.windowConfiguration.getBounds(), - mRenderer.mDividerWidthPx, mProperties.mDividerAttributes, + mProperties.mDividerWidthPx, mProperties.mDividerAttributes, mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout); } @@ -823,13 +834,12 @@ class DividerPresenter implements View.OnTouchListener { * Returns the new split ratio of the {@link SplitContainer} based on the current divider * position. */ - float calculateNewSplitRatio(@NonNull SplitContainer topSplitContainer) { + float calculateNewSplitRatio() { synchronized (mLock) { return calculateNewSplitRatio( - topSplitContainer, mDividerPosition, mProperties.mConfiguration.windowConfiguration.getBounds(), - mRenderer.mDividerWidthPx, + mProperties.mDividerWidthPx, mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout, calculateMinPosition(), @@ -841,21 +851,20 @@ class DividerPresenter implements View.OnTouchListener { private static boolean isDraggingToFullscreenAllowed( @NonNull DividerAttributes dividerAttributes) { // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is - // updated. - return true; + // updated to v7. + return false; } /** * Returns the new split ratio of the {@link SplitContainer} based on the current divider * position. * - * @param topSplitContainer the {@link SplitContainer} for which to compute the split ratio. * @param dividerPosition the divider position. See {@link #mDividerPosition}. * @param taskBounds the task bounds * @param dividerWidthPx the width of the divider in pixels. * @param isVerticalSplit if {@code true}, the split is a vertical split. If {@code false}, the * split is a horizontal split. See - * {@link #isVerticalSplit(SplitContainer)}. + * {@link #isVerticalSplit(SplitAttributes)}. * @param isReversedLayout if {@code true}, the split layout is reversed, i.e. right-to-left or * bottom-to-top. If {@code false}, the split is not reversed, i.e. * left-to-right or top-to-bottom. See @@ -866,7 +875,6 @@ class DividerPresenter implements View.OnTouchListener { */ @VisibleForTesting static float calculateNewSplitRatio( - @NonNull SplitContainer topSplitContainer, int dividerPosition, @NonNull Rect taskBounds, int dividerWidthPx, @@ -891,8 +899,6 @@ class DividerPresenter implements View.OnTouchListener { dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition); } - final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer(); - final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds(); final int usableSize = isVerticalSplit ? taskBounds.width() - dividerWidthPx : taskBounds.height() - dividerWidthPx; @@ -900,13 +906,13 @@ class DividerPresenter implements View.OnTouchListener { final float newRatio; if (isVerticalSplit) { final int newPrimaryWidth = isReversedLayout - ? (origPrimaryBounds.right - (dividerPosition + dividerWidthPx)) - : (dividerPosition - origPrimaryBounds.left); + ? taskBounds.width() - (dividerPosition + dividerWidthPx) + : dividerPosition; newRatio = 1.0f * newPrimaryWidth / usableSize; } else { final int newPrimaryHeight = isReversedLayout - ? (origPrimaryBounds.bottom - (dividerPosition + dividerWidthPx)) - : (dividerPosition - origPrimaryBounds.top); + ? taskBounds.height() - (dividerPosition + dividerWidthPx) + : dividerPosition; newRatio = 1.0f * newPrimaryHeight / usableSize; } return newRatio; @@ -961,6 +967,7 @@ class DividerPresenter implements View.OnTouchListener { private final boolean mIsDraggableExpandType; private final Color mPrimaryVeilColor; private final Color mSecondaryVeilColor; + private final int mDividerWidthPx; @VisibleForTesting Properties( @@ -984,6 +991,7 @@ class DividerPresenter implements View.OnTouchListener { mIsDraggableExpandType = isDraggableExpandType; mPrimaryVeilColor = primaryVeilColor; mSecondaryVeilColor = secondaryVeilColor; + mDividerWidthPx = getDividerWidthPx(dividerAttributes); } /** @@ -1050,7 +1058,6 @@ class DividerPresenter implements View.OnTouchListener { private final View.OnTouchListener mListener; @NonNull private Properties mProperties; - private int mDividerWidthPx; private int mHandleWidthPx; @Nullable private SurfaceControl mPrimaryVeil; @@ -1090,7 +1097,6 @@ class DividerPresenter implements View.OnTouchListener { /** Updates the divider when initializing or when properties are changed */ @VisibleForTesting void update() { - mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes); mDividerPosition = mProperties.mInitialDividerPosition; mWindowlessWindowManager.setConfiguration(mProperties.mConfiguration); @@ -1156,15 +1162,17 @@ class DividerPresenter implements View.OnTouchListener { // When the divider drag handle width is larger than the divider width, the position // of the divider surface is adjusted so that it is large enough to host both the // divider line and the divider drag handle. - mDividerSurfaceWidthPx = Math.max(mDividerWidthPx, mHandleWidthPx); + mDividerSurfaceWidthPx = Math.max(mProperties.mDividerWidthPx, mHandleWidthPx); + dividerSurfacePosition = mProperties.mIsReversedLayout + ? mDividerPosition + : mDividerPosition + mProperties.mDividerWidthPx - mDividerSurfaceWidthPx; dividerSurfacePosition = - mProperties.mIsReversedLayout - ? mDividerPosition - : mDividerPosition + mDividerWidthPx - mDividerSurfaceWidthPx; - dividerSurfacePosition = Math.clamp(dividerSurfacePosition, 0, - mProperties.mIsVerticalSplit ? taskBounds.width() : taskBounds.height()); + Math.clamp(dividerSurfacePosition, 0, + mProperties.mIsVerticalSplit + ? taskBounds.width() - mDividerSurfaceWidthPx + : taskBounds.height() - mDividerSurfaceWidthPx); } else { - mDividerSurfaceWidthPx = mDividerWidthPx; + mDividerSurfaceWidthPx = mProperties.mDividerWidthPx; dividerSurfacePosition = mDividerPosition; } @@ -1177,16 +1185,9 @@ class DividerPresenter implements View.OnTouchListener { } // Update divider line position in the surface - if (!mProperties.mIsReversedLayout) { - final int offset = mDividerPosition - dividerSurfacePosition; - mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0); - mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset); - } else { - // For reversed layout, the divider line is always at the start of the divider - // surface. - mDividerLine.setX(0); - mDividerLine.setY(0); - } + final int offset = mDividerPosition - dividerSurfacePosition; + mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0); + mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset); if (mIsDragging) { updateVeils(t); @@ -1236,8 +1237,10 @@ class DividerPresenter implements View.OnTouchListener { final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); mDividerLine.setLayoutParams( mProperties.mIsVerticalSplit - ? new FrameLayout.LayoutParams(mDividerWidthPx, taskBounds.height()) - : new FrameLayout.LayoutParams(taskBounds.width(), mDividerWidthPx) + ? new FrameLayout.LayoutParams( + mProperties.mDividerWidthPx, taskBounds.height()) + : new FrameLayout.LayoutParams( + taskBounds.width(), mProperties.mDividerWidthPx) ); if (mProperties.mDividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { @@ -1347,13 +1350,14 @@ class DividerPresenter implements View.OnTouchListener { Rect secondaryBounds; if (mProperties.mIsVerticalSplit) { final Rect boundsLeft = new Rect(0, 0, mDividerPosition, taskBounds.height()); - final Rect boundsRight = new Rect(mDividerPosition + mDividerWidthPx, 0, + final Rect boundsRight = new Rect(mDividerPosition + mProperties.mDividerWidthPx, 0, taskBounds.width(), taskBounds.height()); primaryBounds = mProperties.mIsReversedLayout ? boundsRight : boundsLeft; secondaryBounds = mProperties.mIsReversedLayout ? boundsLeft : boundsRight; } else { final Rect boundsTop = new Rect(0, 0, taskBounds.width(), mDividerPosition); - final Rect boundsBottom = new Rect(0, mDividerPosition + mDividerWidthPx, + final Rect boundsBottom = new Rect( + 0, mDividerPosition + mProperties.mDividerWidthPx, taskBounds.width(), taskBounds.height()); primaryBounds = mProperties.mIsReversedLayout ? boundsBottom : boundsTop; secondaryBounds = mProperties.mIsReversedLayout ? boundsTop : boundsBottom; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 9aa12aa824df..f78e2b5170fc 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -1322,7 +1322,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mPresenter.expandTaskFragment(wct, container); } else { // Put activity into a new expanded container. - final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity)); + final TaskFragmentContainer newContainer = + new TaskFragmentContainer.Builder(this, getTaskId(activity), activity) + .setPendingAppearedActivity(activity).build(); mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity); } } @@ -1738,9 +1740,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Can't find any activity in the Task that we can use as the owner activity. return null; } - final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */, - intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag, - launchOptions, associateLaunchingActivity); + final TaskFragmentContainer container = + new TaskFragmentContainer.Builder(this, taskId, activityInTask) + .setPendingAppearedIntent(intent) + .setOverlayTag(overlayTag) + .setLaunchOptions(launchOptions) + .setAssociatedActivity(associateLaunchingActivity ? activityInTask : null) + .build(); final IBinder taskFragmentToken = container.getTaskFragmentToken(); // Note that taskContainer will not exist before calling #newContainer if the container // is the first embedded TF in the task. @@ -1818,74 +1824,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) { - return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId); - } - - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, - @NonNull Activity activityInTask, int taskId) { - return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, - activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */, - null /* launchOptions */, false /* associateLaunchingActivity */); - } - - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, - @NonNull Activity activityInTask, int taskId) { - return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, - activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */, - null /* launchOptions */, false /* associateLaunchingActivity */); - } - - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, - @NonNull Activity activityInTask, int taskId, - @NonNull TaskFragmentContainer pairedPrimaryContainer) { - return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, - activityInTask, taskId, pairedPrimaryContainer, null /* tag */, - null /* launchOptions */, false /* associateLaunchingActivity */); - } - - /** - * Creates and registers a new organized container with an optional activity that will be - * re-parented to it in a WCT. - * - * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment. - * @param pendingAppearedIntent the Intent that will be started in the TaskFragment. - * @param activityInTask activity in the same Task so that we can get the Task bounds - * if needed. - * @param taskId parent Task of the new TaskFragment. - * @param pairedContainer the paired primary {@link TaskFragmentContainer}. When it is - * set, the new container will be added right above it. - * @param overlayTag The tag for the new created overlay container. It must be - * needed if {@code isOverlay} is {@code true}. Otherwise, - * it should be {@code null}. - * @param launchOptions The launch options bundle to create a container. Must be - * specified for overlay container. - * @param associateLaunchingActivity {@code true} to indicate this overlay container - * should associate with launching activity. - */ - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, - @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId, - @Nullable TaskFragmentContainer pairedContainer, @Nullable String overlayTag, - @Nullable Bundle launchOptions, boolean associateLaunchingActivity) { - if (activityInTask == null) { - throw new IllegalArgumentException("activityInTask must not be null,"); - } - if (!mTaskContainers.contains(taskId)) { - mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask)); - mDividerPresenters.put(taskId, new DividerPresenter(taskId, this, mExecutor)); - } - final TaskContainer taskContainer = mTaskContainers.get(taskId); - final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, - pendingAppearedIntent, taskContainer, this, pairedContainer, overlayTag, - launchOptions, associateLaunchingActivity ? activityInTask : null); - return container; - } - /** * Creates and registers a new split with the provided containers and configuration. Finishes * existing secondary containers if found for the given primary container. @@ -2581,6 +2519,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return mTaskContainers.get(taskId); } + @GuardedBy("mLock") + void addTaskContainer(int taskId, TaskContainer taskContainer) { + mTaskContainers.put(taskId, taskContainer); + mDividerPresenters.put(taskId, new DividerPresenter(taskId, this, mExecutor)); + } + Handler getHandler() { return mHandler; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 1cb410e90a76..eade86e50659 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -186,8 +186,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Create new empty task fragment final int taskId = primaryContainer.getTaskId(); - final TaskFragmentContainer secondaryContainer = mController.newContainer( - secondaryIntent, primaryActivity, taskId); + final TaskFragmentContainer secondaryContainer = + new TaskFragmentContainer.Builder(mController, taskId, primaryActivity) + .setPendingAppearedIntent(secondaryIntent).build(); final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes); final int windowingMode = mController.getTaskContainer(taskId) @@ -261,7 +262,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { TaskFragmentContainer container = mController.getContainerWithActivity(activity); final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); if (container == null || container == containerToAvoid) { - container = mController.newContainer(activity, taskId); + container = new TaskFragmentContainer.Builder(mController, taskId, activity) + .setPendingAppearedActivity(activity).build(); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForTaskFragment(relBounds); final IBinder reparentActivityToken = activity.getActivityToken(); @@ -304,15 +306,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( launchingActivity); if (primaryContainer == null) { - primaryContainer = mController.newContainer(launchingActivity, - launchingActivity.getTaskId()); + primaryContainer = new TaskFragmentContainer.Builder(mController, + launchingActivity.getTaskId(), launchingActivity) + .setPendingAppearedActivity(launchingActivity).build(); } final int taskId = primaryContainer.getTaskId(); - final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent, - launchingActivity, taskId, - // Pass in the primary container to make sure it is added right above the primary. - primaryContainer); + final TaskFragmentContainer secondaryContainer = + new TaskFragmentContainer.Builder(mController, taskId, launchingActivity) + .setPendingAppearedIntent(activityIntent) + // Pass in the primary container to make sure it is added right above the + // primary. + .setPairedPrimaryContainer(primaryContainer) + .build(); final TaskContainer taskContainer = mController.getTaskContainer(taskId); final int windowingMode = taskContainer.getWindowingModeForTaskFragment( primaryRelBounds); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index c708da97d908..ee00c4cd67eb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -510,7 +510,7 @@ class TaskContainer { return; } final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer(); - final float newRatio = dividerPresenter.calculateNewSplitRatio(topSplitContainer); + final float newRatio = dividerPresenter.calculateNewSplitRatio(); // If the primary container is fully expanded, we should finish all the associated // secondary containers. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index d0b6a01bb51e..7173b0c95230 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -195,20 +195,6 @@ class TaskFragmentContainer { private boolean mLastDimOnTask; /** - * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController, - * TaskFragmentContainer, String, Bundle, Activity) - */ - TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, - @Nullable Intent pendingAppearedIntent, - @NonNull TaskContainer taskContainer, - @NonNull SplitController controller, - @Nullable TaskFragmentContainer pairedPrimaryContainer) { - this(pendingAppearedActivity, pendingAppearedIntent, taskContainer, - controller, pairedPrimaryContainer, null /* overlayTag */, - null /* launchOptions */, null /* associatedActivity */); - } - - /** * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. * @param pairedPrimaryContainer when it is set, the new container will be add right above it @@ -218,7 +204,7 @@ class TaskFragmentContainer { * @param associatedActivity the associated activity of the overlay container. Must be * {@code null} for a non-overlay container. */ - TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, + private TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller, @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag, @@ -232,12 +218,6 @@ class TaskFragmentContainer { mToken = new Binder("TaskFragmentContainer"); mTaskContainer = taskContainer; mOverlayTag = overlayTag; - if (overlayTag != null) { - Objects.requireNonNull(launchOptions); - } else if (associatedActivity != null) { - throw new IllegalArgumentException("Associated activity must be null for " - + "non-overlay activity."); - } mAssociatedActivityToken = associatedActivity != null ? associatedActivity.getActivityToken() : null; @@ -1116,6 +1096,117 @@ class TaskFragmentContainer { return sb.append("]").toString(); } + static final class Builder { + @NonNull + private final SplitController mSplitController; + + // The parent Task id of the new TaskFragment. + private final int mTaskId; + + // The activity in the same Task so that we can get the Task bounds if needed. + @NonNull + private final Activity mActivityInTask; + + // The activity that will be reparented to the TaskFragment. + @Nullable + private Activity mPendingAppearedActivity; + + // The Intent that will be started in the TaskFragment. + @Nullable + private Intent mPendingAppearedIntent; + + // The paired primary {@link TaskFragmentContainer}. When it is set, the new container + // will be added right above it. + @Nullable + private TaskFragmentContainer mPairedPrimaryContainer; + + // The launch options bundle to create a container. Must be specified for overlay container. + @Nullable + private Bundle mLaunchOptions; + + // The tag for the new created overlay container. This is required when creating an + // overlay container. + @Nullable + private String mOverlayTag; + + // The associated activity of the overlay container. Must be {@code null} for a + // non-overlay container. + @Nullable + private Activity mAssociatedActivity; + + Builder(@NonNull SplitController splitController, int taskId, + @Nullable Activity activityInTask) { + if (taskId <= 0) { + throw new IllegalArgumentException("taskId is invalid, " + taskId); + } + + mSplitController = splitController; + mTaskId = taskId; + mActivityInTask = activityInTask; + } + + @NonNull + Builder setPendingAppearedActivity(@Nullable Activity pendingAppearedActivity) { + mPendingAppearedActivity = pendingAppearedActivity; + return this; + } + + @NonNull + Builder setPendingAppearedIntent(@Nullable Intent pendingAppearedIntent) { + mPendingAppearedIntent = pendingAppearedIntent; + return this; + } + + @NonNull + Builder setPairedPrimaryContainer(@Nullable TaskFragmentContainer pairedPrimaryContainer) { + mPairedPrimaryContainer = pairedPrimaryContainer; + return this; + } + + @NonNull + Builder setLaunchOptions(@Nullable Bundle launchOptions) { + mLaunchOptions = launchOptions; + return this; + } + + @NonNull + Builder setOverlayTag(@Nullable String overlayTag) { + mOverlayTag = overlayTag; + return this; + } + + @NonNull + Builder setAssociatedActivity(@Nullable Activity associatedActivity) { + mAssociatedActivity = associatedActivity; + return this; + } + + @NonNull + TaskFragmentContainer build() { + if (mOverlayTag != null) { + Objects.requireNonNull(mLaunchOptions); + } else if (mAssociatedActivity != null) { + throw new IllegalArgumentException("Associated activity must be null for " + + "non-overlay activity."); + } + + TaskContainer taskContainer = mSplitController.getTaskContainer(mTaskId); + if (taskContainer == null && mActivityInTask == null) { + throw new IllegalArgumentException("mActivityInTask must be set."); + } + + if (taskContainer == null) { + // Adding a TaskContainer if no existed one. + taskContainer = new TaskContainer(mTaskId, mActivityInTask); + mSplitController.addTaskContainer(mTaskId, taskContainer); + } + + return new TaskFragmentContainer(mPendingAppearedActivity, mPendingAppearedIntent, + taskContainer, mSplitController, mPairedPrimaryContainer, mOverlayTag, + mLaunchOptions, mAssociatedActivity); + } + } + static class OverlayContainerRestoreParams { /** The token of the overlay container */ @NonNull diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index 746607c8094c..4515187f231e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -144,6 +144,7 @@ public class DividerPresenterTest { new SplitAttributes.Builder() .setDividerAttributes(DEFAULT_DIVIDER_ATTRIBUTES) .build()); + final Rect mockTaskBounds = new Rect(0, 0, 2000, 1000); final TaskFragmentContainer mockPrimaryContainer = createMockTaskFragmentContainer( mPrimaryContainerToken, new Rect(0, 0, 950, 1000)); @@ -158,7 +159,9 @@ public class DividerPresenterTest { DEFAULT_DIVIDER_ATTRIBUTES, mSurfaceControl, getInitialDividerPosition( - mSplitContainer, true /* isVerticalSplit */, false /* isReversedLayout */), + mockPrimaryContainer, mockSecondaryContainer, mockTaskBounds, + 50 /* divideWidthPx */, false /* isDraggableExpandType */, + true /* isVerticalSplit */, false /* isReversedLayout */), true /* isVerticalSplit */, false /* isReversedLayout */, Display.DEFAULT_DISPLAY, @@ -502,7 +505,6 @@ public class DividerPresenterTest { assertEquals( 0.3f, // Primary is 300px after dragging. DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -518,7 +520,6 @@ public class DividerPresenterTest { assertEquals( DividerPresenter.RATIO_EXPANDED_SECONDARY, DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -535,7 +536,6 @@ public class DividerPresenterTest { assertEquals( 0.2f, // Adjusted to the minPosition 200 DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -569,7 +569,6 @@ public class DividerPresenterTest { // After dragging, secondary is [0, 0, 2000, 300]. Primary is [0, 400, 2000, 1100]. 0.7f, DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -587,7 +586,6 @@ public class DividerPresenterTest { // The primary (bottom) container is expanded DividerPresenter.RATIO_EXPANDED_PRIMARY, DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -605,7 +603,6 @@ public class DividerPresenterTest { // Adjusted to minPosition 200, so the primary (bottom) container is 800. 0.8f, DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -720,7 +717,7 @@ public class DividerPresenterTest { // Divider position is greater than minPosition and the velocity is enough for fling assertEquals( - 0, // Closed position + 30, // minPosition DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( 50 /* dividerPosition */, 30 /* minPosition */, @@ -731,7 +728,7 @@ public class DividerPresenterTest { // Divider position is less than maxPosition and the velocity is enough for fling assertEquals( - 1200, // Fully expanded position + 900, // maxPosition DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( 800 /* dividerPosition */, 30 /* minPosition */, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index a069ac7256d6..d649c6d57137 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -248,4 +248,17 @@ public class EmbeddingTestUtils { return new SplitPlaceholderRule.Builder(placeholderIntent, activityPredicate, intentPredicate, windowMetricsPredicate); } + + @NonNull + static TaskFragmentContainer createTfContainer( + @NonNull SplitController splitController, @NonNull Activity activity) { + return createTfContainer(splitController, TASK_ID, activity); + } + + @NonNull + static TaskFragmentContainer createTfContainer( + @NonNull SplitController splitController, int taskId, @NonNull Activity activity) { + return new TaskFragmentContainer.Builder(splitController, taskId, activity) + .setPendingAppearedActivity(activity).build(); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index 76e6a0ff2c21..7b473b04548c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -105,8 +106,11 @@ public class JetpackTaskFragmentOrganizerTest { @Test public void testExpandTaskFragment() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */); + doReturn(taskContainer).when(mSplitController).getTaskContainer(anyInt()); + final TaskFragmentContainer container = new TaskFragmentContainer.Builder(mSplitController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .build(); final TaskFragmentInfo info = createMockInfo(container); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); container.setInfo(mTransaction, info); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 86b7e88a0c1a..0972d40f33e3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -29,6 +29,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMock import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; @@ -530,8 +531,8 @@ public class OverlayPresentationTest { @Test public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() { - final TaskFragmentContainer container = mSplitController.newContainer(mActivity, - mActivity.getTaskId()); + final TaskFragmentContainer container = createTfContainer(mSplitController, + mActivity.getTaskId(), mActivity); mSplitController.updateActivityStackAttributes( ActivityStack.Token.createFromBinder(container.getTaskFragmentToken()), new ActivityStackAttributes.Builder().build()); @@ -837,8 +838,9 @@ public class OverlayPresentationTest { final Intent intent = new Intent(); final IBinder fillTaskActivityToken = new Binder(); final IBinder lastOverlayToken = new Binder(); - final TaskFragmentContainer overlayContainer = mSplitController.newContainer(intent, - mActivity, TASK_ID); + final TaskFragmentContainer overlayContainer = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(intent).build(); final TaskFragmentContainer.OverlayContainerRestoreParams params = mock( TaskFragmentContainer.OverlayContainerRestoreParams.class); doReturn(params).when(mSplitController).getOverlayContainerRestoreParams(any(), any()); @@ -884,8 +886,8 @@ public class OverlayPresentationTest { @NonNull private TaskFragmentContainer createMockTaskFragmentContainer( @NonNull Activity activity, boolean isVisible) { - final TaskFragmentContainer container = mSplitController.newContainer(activity, - activity.getTaskId()); + final TaskFragmentContainer container = createTfContainer(mSplitController, + activity.getTaskId(), activity); setupTaskFragmentInfo(container, activity, isVisible); return container; } @@ -918,10 +920,13 @@ public class OverlayPresentationTest { @Nullable Activity launchingActivity) { final Activity activity = launchingActivity != null ? launchingActivity : createMockActivity(); - TaskFragmentContainer overlayContainer = mSplitController.newContainer( - null /* pendingAppearedActivity */, mIntent, activity, taskId, - null /* pairedPrimaryContainer */, tag, Bundle.EMPTY, - associateLaunchingActivity); + TaskFragmentContainer overlayContainer = + new TaskFragmentContainer.Builder(mSplitController, taskId, activity) + .setPendingAppearedIntent(mIntent) + .setOverlayTag(tag) + .setLaunchOptions(Bundle.EMPTY) + .setAssociatedActivity(associateLaunchingActivity ? activity : null) + .build(); setupTaskFragmentInfo(overlayContainer, createMockActivity(), isVisible); return overlayContainer; } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 35353dbe36be..640b1fced455 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -41,6 +41,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSpli import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; @@ -59,7 +60,6 @@ import static org.junit.Assert.assertTrue; 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.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; @@ -198,7 +198,7 @@ public class SplitControllerTest { @Test public void testOnTaskFragmentVanished() { - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken(); // The TaskFragment has been removed in the server, we only need to cleanup the reference. @@ -213,7 +213,7 @@ public class SplitControllerTest { public void testOnTaskFragmentAppearEmptyTimeout() { // Setup to make sure a transaction record is started. mTransactionManager.startNewTransaction(); - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any()); mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf); @@ -224,7 +224,7 @@ public class SplitControllerTest { @Test public void testOnActivityDestroyed() { doReturn(new Binder()).when(mActivity).getActivityToken(); - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); assertTrue(tf.hasActivity(mActivity.getActivityToken())); @@ -245,12 +245,9 @@ public class SplitControllerTest { public void testNewContainer() { // Must pass in a valid activity. assertThrows(IllegalArgumentException.class, () -> - mSplitController.newContainer(null /* activity */, TASK_ID)); - assertThrows(IllegalArgumentException.class, () -> - mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID)); + createTfContainer(mSplitController, null /* activity */)); - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, mActivity, - TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); assertNotNull(tf); @@ -263,7 +260,7 @@ public class SplitControllerTest { public void testUpdateContainer() { // Make SplitController#launchPlaceholderIfNecessary(TaskFragmentContainer) return true // and verify if shouldContainerBeExpanded() not called. - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); spyOn(tf); doReturn(mActivity).when(tf).getTopNonFinishingActivity(); doReturn(true).when(tf).isEmpty(); @@ -369,8 +366,12 @@ public class SplitControllerTest { public void testOnStartActivityResultError() { final Intent intent = new Intent(); final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - intent, taskContainer, mSplitController, null /* pairedPrimaryContainer */); + final int taskId = taskContainer.getTaskId(); + mSplitController.addTaskContainer(taskId, taskContainer); + final TaskFragmentContainer container = new TaskFragmentContainer.Builder(mSplitController, + taskId, null /* activityInTask */) + .setPendingAppearedIntent(intent) + .build(); final SplitController.ActivityStartMonitor monitor = mSplitController.getActivityStartMonitor(); @@ -410,7 +411,8 @@ public class SplitControllerTest { @Test public void testOnActivityReparentedToTask_diffProcess() { // Create an empty TaskFragment to initialize for the Task. - mSplitController.newContainer(new Intent(), mActivity, TASK_ID); + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(new Intent()).build(); final IBinder activityToken = new Binder(); final Intent intent = new Intent(); @@ -595,8 +597,9 @@ public class SplitControllerTest { verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any()); // Place in the top container if there is no other rule matched. - final TaskFragmentContainer topContainer = mSplitController - .newContainer(new Intent(), mActivity, TASK_ID); + final TaskFragmentContainer topContainer = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(new Intent()).build(); mSplitController.placeActivityInTopContainer(mTransaction, mActivity); verify(mTransaction).reparentActivityToTaskFragment(topContainer.getTaskFragmentToken(), @@ -604,7 +607,7 @@ public class SplitControllerTest { // Not reparent if activity is in a TaskFragment. clearInvocations(mTransaction); - mSplitController.newContainer(mActivity, TASK_ID); + createTfContainer(mSplitController, mActivity); mSplitController.placeActivityInTopContainer(mTransaction, mActivity); verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any()); @@ -616,8 +619,7 @@ public class SplitControllerTest { false /* isOnReparent */); assertFalse(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString(), any(), anyBoolean()); + verify(mSplitController, never()).addTaskContainer(anyInt(), any()); } @Test @@ -632,7 +634,6 @@ public class SplitControllerTest { assertTrue(result); assertNotNull(container); - verify(mSplitController).newContainer(mActivity, TASK_ID); verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(), mActivity); } @@ -642,7 +643,7 @@ public class SplitControllerTest { setupExpandRule(mActivity); // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it. - final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, mActivity); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -692,8 +693,8 @@ public class SplitControllerTest { // Don't launch placeholder if the activity is not in the topmost active TaskFragment. final Activity activity = createMockActivity(); - mSplitController.newContainer(mActivity, TASK_ID); - mSplitController.newContainer(activity, TASK_ID); + createTfContainer(mSplitController, mActivity); + createTfContainer(mSplitController, activity); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -711,7 +712,7 @@ public class SplitControllerTest { (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); // Launch placeholder if the activity is in the topmost expanded TaskFragment. - mSplitController.newContainer(mActivity, TASK_ID); + createTfContainer(mSplitController, mActivity); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -763,10 +764,11 @@ public class SplitControllerTest { final SplitPairRule splitRule = (SplitPairRule) mSplitController.getSplitRules().get(0); // Activity is already in primary split, no need to create new split. - final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, - TASK_ID); - final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( - secondaryIntent, mActivity, TASK_ID); + final TaskFragmentContainer primaryContainer = + createTfContainer(mSplitController, mActivity); + final TaskFragmentContainer secondaryContainer = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(secondaryIntent).build(); mSplitController.registerSplit( mTransaction, primaryContainer, @@ -779,8 +781,6 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString(), any(), anyBoolean()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @@ -792,10 +792,11 @@ public class SplitControllerTest { // The new launched activity is in primary split, but there is no rule for it to split with // the secondary, so return false. - final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, - TASK_ID); - final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( - secondaryIntent, mActivity, TASK_ID); + final TaskFragmentContainer primaryContainer = + createTfContainer(mSplitController, mActivity); + final TaskFragmentContainer secondaryContainer = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(secondaryIntent).build(); mSplitController.registerSplit( mTransaction, primaryContainer, @@ -822,8 +823,6 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString(), any(), anyBoolean()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @@ -852,10 +851,10 @@ public class SplitControllerTest { doReturn(PLACEHOLDER_INTENT).when(mActivity).getIntent(); // Activity is a placeholder. - final TaskFragmentContainer primaryContainer = mSplitController.newContainer( - primaryActivity, TASK_ID); - final TaskFragmentContainer secondaryContainer = mSplitController.newContainer(mActivity, - TASK_ID); + final TaskFragmentContainer primaryContainer = + createTfContainer(mSplitController, primaryActivity); + final TaskFragmentContainer secondaryContainer = + createTfContainer(mSplitController, mActivity); mSplitController.registerSplit( mTransaction, primaryContainer, @@ -874,8 +873,7 @@ public class SplitControllerTest { final Activity activityBelow = createMockActivity(); setupSplitRule(activityBelow, mActivity); - final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, - TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, activityBelow); container.addPendingAppearedActivity(mActivity); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -890,8 +888,7 @@ public class SplitControllerTest { setupSplitRule(mActivity, activityBelow); // Disallow to split as primary. - final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, - TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, activityBelow); container.addPendingAppearedActivity(mActivity); boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -961,8 +958,7 @@ public class SplitControllerTest { doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); - final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, - TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, activityBelow); container.addPendingAppearedActivity(mActivity); // Allow to split as primary. @@ -980,8 +976,7 @@ public class SplitControllerTest { doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); - final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, - TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, activityBelow); container.addPendingAppearedActivity(mActivity); boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, @@ -1044,8 +1039,8 @@ public class SplitControllerTest { public void testResolveActivityToContainer_skipIfNonTopOrPinned() { final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); final Activity pinnedActivity = createMockActivity(); - final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity, - TASK_ID); + final TaskFragmentContainer topContainer = + createTfContainer(mSplitController, pinnedActivity); final TaskContainer taskContainer = container.getTaskContainer(); spyOn(taskContainer); doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false); @@ -1351,7 +1346,7 @@ public class SplitControllerTest { // Launch placeholder for activity in top TaskFragment. setupPlaceholderRule(mActivity); mTransactionManager.startNewTransaction(); - final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, mActivity); mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, true /* isOnCreated */); @@ -1365,9 +1360,10 @@ public class SplitControllerTest { // Do not launch placeholder for invisible activity below the top TaskFragment. setupPlaceholderRule(mActivity); mTransactionManager.startNewTransaction(); - final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, - TASK_ID); + final TaskFragmentContainer bottomTf = createTfContainer(mSplitController, mActivity); + final TaskFragmentContainer topTf = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(new Intent()).build(); bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, false /* isVisible */)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); @@ -1383,9 +1379,10 @@ public class SplitControllerTest { // Launch placeholder for visible activity below the top TaskFragment. setupPlaceholderRule(mActivity); mTransactionManager.startNewTransaction(); - final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, - TASK_ID); + final TaskFragmentContainer bottomTf = createTfContainer(mSplitController, mActivity); + final TaskFragmentContainer topTf = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(new Intent()).build(); bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, true /* isVisible */)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); @@ -1412,7 +1409,7 @@ public class SplitControllerTest { @Test public void testFinishActivityStacks_finishSingleActivityStack() { - TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity)); final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID); @@ -1426,8 +1423,8 @@ public class SplitControllerTest { @Test public void testFinishActivityStacks_finishActivityStacksInOrder() { - TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); - TaskFragmentContainer topTf = mSplitController.newContainer(mActivity, TASK_ID); + TaskFragmentContainer bottomTf = createTfContainer(mSplitController, mActivity); + TaskFragmentContainer topTf = createTfContainer(mSplitController, mActivity); bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); @@ -1687,8 +1684,8 @@ public class SplitControllerTest { /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) { - final TaskFragmentContainer container = mSplitController.newContainer(activity, - activity.getTaskId()); + final TaskFragmentContainer container = createTfContainer(mSplitController, + activity.getTaskId(), activity); setupTaskFragmentInfo(container, activity); return container; } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 3fbce9ec31a5..816e2dae1e5b 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -31,6 +31,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActi import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.SplitPresenter.EXPAND_CONTAINERS_ATTRIBUTES; @@ -139,7 +140,7 @@ public class SplitPresenterTest { @Test public void testCreateTaskFragment() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); mPresenter.createTaskFragment(mTransaction, container.getTaskFragmentToken(), mActivity.getActivityToken(), TASK_BOUNDS, WINDOWING_MODE_MULTI_WINDOW); @@ -150,7 +151,7 @@ public class SplitPresenterTest { @Test public void testResizeTaskFragment() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); mPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), mTaskFragmentInfo); mPresenter.resizeTaskFragment(mTransaction, container.getTaskFragmentToken(), TASK_BOUNDS); @@ -166,7 +167,7 @@ public class SplitPresenterTest { @Test public void testUpdateWindowingMode() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); mPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), mTaskFragmentInfo); mPresenter.updateWindowingMode(mTransaction, container.getTaskFragmentToken(), WINDOWING_MODE_MULTI_WINDOW); @@ -184,8 +185,8 @@ public class SplitPresenterTest { @Test public void testSetAdjacentTaskFragments() { - final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container0 = createTfContainer(mController, mActivity); + final TaskFragmentContainer container1 = createTfContainer(mController, mActivity); mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(), container1.getTaskFragmentToken(), null /* adjacentParams */); @@ -202,8 +203,8 @@ public class SplitPresenterTest { @Test public void testClearAdjacentTaskFragments() { - final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container0 = createTfContainer(mController, mActivity); + final TaskFragmentContainer container1 = createTfContainer(mController, mActivity); // No request to clear as it is not set by default. mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken()); @@ -224,8 +225,8 @@ public class SplitPresenterTest { @Test public void testSetCompanionTaskFragment() { - final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container0 = createTfContainer(mController, mActivity); + final TaskFragmentContainer container1 = createTfContainer(mController, mActivity); mPresenter.setCompanionTaskFragment(mTransaction, container0.getTaskFragmentToken(), container1.getTaskFragmentToken()); @@ -242,7 +243,7 @@ public class SplitPresenterTest { @Test public void testSetTaskFragmentDimOnTask() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); mPresenter.setTaskFragmentDimOnTask(mTransaction, container.getTaskFragmentToken(), true); verify(mTransaction).addTaskFragmentOperation(eq(container.getTaskFragmentToken()), any()); @@ -255,7 +256,7 @@ public class SplitPresenterTest { @Test public void testUpdateAnimationParams() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); // Verify the default. assertTrue(container.areLastRequestedAnimationParamsEqual( @@ -287,7 +288,7 @@ public class SplitPresenterTest { @Test public void testSetTaskFragmentPinned() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); // Verify the default. assertFalse(container.isPinned()); @@ -667,8 +668,8 @@ public class SplitPresenterTest { public void testExpandSplitContainerIfNeeded() { Activity secondaryActivity = createMockActivity(); SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); - TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); - TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID); + TaskFragmentContainer primaryTf = createTfContainer(mController, mActivity); + TaskFragmentContainer secondaryTf = createTfContainer(mController, secondaryActivity); SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity, secondaryTf, splitRule, SPLIT_ATTRIBUTES); @@ -710,8 +711,8 @@ public class SplitPresenterTest { @Test public void testCreateNewSplitContainer_secondaryAbovePrimary() { final Activity secondaryActivity = createMockActivity(); - final TaskFragmentContainer bottomTf = mController.newContainer(secondaryActivity, TASK_ID); - final TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer bottomTf = createTfContainer(mController, secondaryActivity); + final TaskFragmentContainer primaryTf = createTfContainer(mController, mActivity); final SplitPairRule rule = createSplitPairRuleBuilder(pair -> pair.first == mActivity && pair.second == secondaryActivity, pair -> false, metrics -> true) diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index 8913b22115e9..284723279b80 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -57,6 +58,9 @@ import java.util.List; * Build/Install/Run: * atest WMJetpackUnitTests:TaskContainerTest */ + +// Suppress GuardedBy warning on unit tests +@SuppressWarnings("GuardedBy") @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) @@ -126,8 +130,11 @@ public class TaskContainerTest { assertTrue(taskContainer.isEmpty()); - final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */, - new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */); + doReturn(taskContainer).when(mController).getTaskContainer(anyInt()); + final TaskFragmentContainer tf = new TaskFragmentContainer.Builder(mController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .build(); assertFalse(taskContainer.isEmpty()); @@ -142,12 +149,17 @@ public class TaskContainerTest { final TaskContainer taskContainer = createTestTaskContainer(); assertNull(taskContainer.getTopNonFinishingTaskFragmentContainer()); - final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */, - new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */); + doReturn(taskContainer).when(mController).getTaskContainer(anyInt()); + final TaskFragmentContainer tf0 = new TaskFragmentContainer.Builder(mController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .build(); assertEquals(tf0, taskContainer.getTopNonFinishingTaskFragmentContainer()); - final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, - new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer tf1 = new TaskFragmentContainer.Builder(mController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .build(); assertEquals(tf1, taskContainer.getTopNonFinishingTaskFragmentContainer()); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 44ab2c458e39..7fab371cb790 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -100,24 +100,27 @@ public class TaskFragmentContainerTest { @Test public void testNewContainer() { final TaskContainer taskContainer = createTestTaskContainer(); + mController.addTaskContainer(taskContainer.getTaskId(), taskContainer); // One of the activity and the intent must be non-null assertThrows(IllegalArgumentException.class, - () -> new TaskFragmentContainer(null, null, taskContainer, mController, - null /* pairedPrimaryContainer */)); + () -> new TaskFragmentContainer.Builder(mController, taskContainer.getTaskId(), + null /* activityInTask */).build()); // One of the activity and the intent must be null. assertThrows(IllegalArgumentException.class, - () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController, - null /* pairedPrimaryContainer */)); + () -> new TaskFragmentContainer.Builder(mController, taskContainer.getTaskId(), + null /* activityInTask */) + .setPendingAppearedActivity(createMockActivity()) + .setPendingAppearedIntent(mIntent) + .build()); } @Test public void testFinish() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer(taskContainer, + mActivity, null /* pendingAppearedIntent */); doReturn(container).when(mController).getContainerWithActivity(mActivity); // Only remove the activity, but not clear the reference until appeared. @@ -148,15 +151,13 @@ public class TaskFragmentContainerTest { @Test public void testFinish_notFinishActivityThatIsReparenting() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container0 = createTaskFragmentContainer(taskContainer, + mActivity, null /* pendingAppearedIntent */); final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity); container0.setInfo(mTransaction, info); // Request to reparent the activity to a new TaskFragment. - final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container1 = createTaskFragmentContainer(taskContainer, + mActivity, null /* pendingAppearedIntent */); doReturn(container1).when(mController).getContainerWithActivity(mActivity); // The activity is requested to be reparented, so don't finish it. @@ -171,15 +172,13 @@ public class TaskFragmentContainerTest { public void testFinish_alwaysFinishPlaceholder() { // Register container1 as a placeholder final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container0 = createTaskFragmentContainer(taskContainer, + mActivity, null /* pendingAppearedIntent */); final TaskFragmentInfo info0 = createMockTaskFragmentInfo(container0, mActivity); container0.setInfo(mTransaction, info0); final Activity placeholderActivity = createMockActivity(); - final TaskFragmentContainer container1 = new TaskFragmentContainer(placeholderActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container1 = createTaskFragmentContainer(taskContainer, + placeholderActivity, null /* pendingAppearedIntent */); final TaskFragmentInfo info1 = createMockTaskFragmentInfo(container1, placeholderActivity); container1.setInfo(mTransaction, info1); final SplitAttributes splitAttributes = new SplitAttributes.Builder().build(); @@ -207,9 +206,8 @@ public class TaskFragmentContainerTest { public void testSetInfo() { final TaskContainer taskContainer = createTestTaskContainer(); // Pending activity should be cleared when it has appeared on server side. - final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer pendingActivityContainer = createTaskFragmentContainer( + taskContainer, mActivity, null /* pendingAppearedIntent */); assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains( mActivity.getActivityToken())); @@ -221,9 +219,8 @@ public class TaskFragmentContainerTest { assertTrue(pendingActivityContainer.mPendingAppearedActivities.isEmpty()); // Pending intent should be cleared when the container becomes non-empty. - final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer( - null /* pendingAppearedActivity */, mIntent, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer pendingIntentContainer = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent()); @@ -237,8 +234,8 @@ public class TaskFragmentContainerTest { @Test public void testIsWaitingActivityAppear() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); assertTrue(container.isWaitingActivityAppear()); @@ -259,8 +256,8 @@ public class TaskFragmentContainerTest { public void testAppearEmptyTimeout() { doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any()); final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); assertNull(container.mAppearEmptyTimeout); @@ -299,8 +296,8 @@ public class TaskFragmentContainerTest { @Test public void testCollectNonFinishingActivities() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); List<Activity> activities = container.collectNonFinishingActivities(); assertTrue(activities.isEmpty()); @@ -327,8 +324,8 @@ public class TaskFragmentContainerTest { @Test public void testCollectNonFinishingActivities_checkIfStable() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); // In case mInfo is null, collectNonFinishingActivities(true) should return null. List<Activity> activities = @@ -353,8 +350,8 @@ public class TaskFragmentContainerTest { @Test public void testAddPendingActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); assertEquals(1, container.collectNonFinishingActivities().size()); @@ -367,10 +364,10 @@ public class TaskFragmentContainerTest { @Test public void testIsAbove() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); - final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container0 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); + final TaskFragmentContainer container1 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); assertTrue(container1.isAbove(container0)); assertFalse(container0.isAbove(container1)); @@ -379,8 +376,8 @@ public class TaskFragmentContainerTest { @Test public void testGetBottomMostActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); assertEquals(mActivity, container.getBottomMostActivity()); @@ -396,8 +393,8 @@ public class TaskFragmentContainerTest { @Test public void testOnActivityDestroyed() { final TaskContainer taskContainer = createTestTaskContainer(mController); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); final List<IBinder> activities = new ArrayList<>(); activities.add(mActivity.getActivityToken()); @@ -416,8 +413,8 @@ public class TaskFragmentContainerTest { public void testIsInIntermediateState() { // True if no info set. final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); spyOn(taskContainer); doReturn(true).when(taskContainer).isVisible(); @@ -479,8 +476,8 @@ public class TaskFragmentContainerTest { @Test public void testHasAppearedActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); assertFalse(container.hasAppearedActivity(mActivity.getActivityToken())); @@ -496,8 +493,8 @@ public class TaskFragmentContainerTest { @Test public void testHasPendingAppearedActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken())); @@ -513,10 +510,10 @@ public class TaskFragmentContainerTest { @Test public void testHasActivity() { final TaskContainer taskContainer = createTestTaskContainer(mController); - final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); - final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container1 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); + final TaskFragmentContainer container2 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); // Activity is pending appeared on container2. container2.addPendingAppearedActivity(mActivity); @@ -550,17 +547,19 @@ public class TaskFragmentContainerTest { @Test public void testNewContainerWithPairedPrimaryContainer() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer tf0 = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, - null /* pairedPrimaryTaskFragment */); - final TaskFragmentContainer tf1 = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, - null /* pairedPrimaryTaskFragment */); + mController.addTaskContainer(taskContainer.getTaskId(), taskContainer); + final TaskFragmentContainer tf0 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, new Intent()); + final TaskFragmentContainer tf1 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, new Intent()); // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted // right above tf0. - final TaskFragmentContainer tf2 = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, tf0); + final TaskFragmentContainer tf2 = new TaskFragmentContainer.Builder(mController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .setPairedPrimaryContainer(tf0) + .build(); assertEquals(0, taskContainer.indexOf(tf0)); assertEquals(1, taskContainer.indexOf(tf2)); assertEquals(2, taskContainer.indexOf(tf1)); @@ -569,18 +568,15 @@ public class TaskFragmentContainerTest { @Test public void testNewContainerWithPairedPendingAppearedActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer tf0 = new TaskFragmentContainer( - createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryTaskFragment */); - final TaskFragmentContainer tf1 = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, - null /* pairedPrimaryTaskFragment */); + final TaskFragmentContainer tf0 = createTaskFragmentContainer(taskContainer, + createMockActivity(), null /* pendingAppearedIntent */); + final TaskFragmentContainer tf1 = createTaskFragmentContainer(taskContainer, + null /* pendingAppearedActivity */, new Intent()); // When tf2 is created with pendingAppearedActivity, tf2 should be inserted below any // TaskFragment without any Activity. - final TaskFragmentContainer tf2 = new TaskFragmentContainer( - createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryTaskFragment */); + final TaskFragmentContainer tf2 = createTaskFragmentContainer(taskContainer, + createMockActivity(), null /* pendingAppearedIntent */); assertEquals(0, taskContainer.indexOf(tf0)); assertEquals(1, taskContainer.indexOf(tf2)); assertEquals(2, taskContainer.indexOf(tf1)); @@ -589,9 +585,8 @@ public class TaskFragmentContainerTest { @Test public void testIsVisible() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, - null /* pairedPrimaryTaskFragment */); + final TaskFragmentContainer container = createTaskFragmentContainer(taskContainer, + null /* pendingAppearedActivity */, new Intent()); // Not visible when there is not appeared. assertFalse(container.isVisible()); @@ -617,4 +612,14 @@ public class TaskFragmentContainerTest { doReturn(activity).when(mController).getActivity(activityToken); return activity; } + + private TaskFragmentContainer createTaskFragmentContainer(TaskContainer taskContainer, + Activity pendingAppearedActivity, Intent pendingAppearedIntent) { + final int taskId = taskContainer.getTaskId(); + mController.addTaskContainer(taskId, taskContainer); + return new TaskFragmentContainer.Builder(mController, taskId, pendingAppearedActivity) + .setPendingAppearedActivity(pendingAppearedActivity) + .setPendingAppearedIntent(pendingAppearedIntent) + .build(); + } } 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-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 81d066f82261..532ecc64358f 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -56,7 +56,7 @@ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Каб выйсці, правядзіце па экране пальцам знізу ўверх або націсніце ў любым месцы над праграмай"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запусціць рэжым кіравання адной рукой"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Выйсці з рэжыму кіравання адной рукой"</string> - <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Налады ўсплывальных апавяшчэнняў у праграме \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Налады ўсплывальных чатаў у праграме \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Дадатковае меню"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Зноў дадаць у стос"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ад праграмы \"<xliff:g id="APP_NAME">%2$s</xliff:g>\""</string> @@ -70,16 +70,16 @@ <string name="bubbles_app_settings" msgid="3617224938701566416">"Налады \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string> <string name="bubble_dismiss_text" msgid="8816558050659478158">"Адхіліць апавяшчэнне"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не паказваць размову ў выглядзе ўсплывальных апавяшчэнняў"</string> - <string name="bubbles_user_education_title" msgid="2112319053732691899">"Усплывальныя апавяшчэнні"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"Новыя размовы будуць паказвацца як рухомыя значкі ці ўсплывальныя апавяшчэнні. Націсніце, каб адкрыць усплывальнае апавяшчэнне. Перацягніце яго, каб перамясціць."</string> - <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Кіруйце ўсплывальнымі апавяшчэннямі ў любы час"</string> - <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Каб выключыць усплывальныя апавяшчэнні з гэтай праграмы, націсніце \"Кіраваць\""</string> + <string name="bubbles_user_education_title" msgid="2112319053732691899">"Усплывальныя чаты"</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"Новыя размовы будуць паказвацца як рухомыя значкі ці ўсплывальныя чаты. Націсніце, каб адкрыць усплывальны чат. Перацягніце яго, каб перамясціць."</string> + <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Кіруйце ўсплывальнымі чатамі"</string> + <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Каб выключыць усплывальныя чаты з гэтай праграмы, націсніце \"Кіраваць\""</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Зразумела"</string> - <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Няма нядаўніх усплывальных апавяшчэнняў"</string> - <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Нядаўнія і адхіленыя ўсплывальныя апавяшчэнні будуць паказаны тут"</string> - <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Чат з выкарыстаннем усплывальных апавяшчэнняў"</string> + <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Няма нядаўніх усплывальных чатаў"</string> + <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Нядаўнія і адхіленыя ўсплывальныя чаты будуць паказаны тут"</string> + <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Усплывальныя чаты"</string> <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Новыя размовы паказваюцца ў выглядзе значкоў у ніжнім вугле экрана. Націсніце на іх, каб разгарнуць. Перацягніце іх, калі хочаце закрыць."</string> - <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Кіруйце наладамі ўсплывальных апавяшчэнняў у любы час"</string> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Кіруйце наладамі ўсплывальных чатаў у любы час"</string> <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Каб кіраваць усплывальнымі апавяшчэннямі для праграм і размоў, націсніце тут"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Усплывальнае апавяшчэнне"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Кіраваць"</string> 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/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index 6ca6517abbb0..dc022b4afd3b 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -69,7 +69,7 @@ public class TransitionUtil { /** Returns {@code true} if the transition is opening or closing mode. */ public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) { - return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK; + return isOpeningMode(mode) || isClosingMode(mode); } /** Returns {@code true} if the transition is opening mode. */ @@ -77,6 +77,11 @@ public class TransitionUtil { return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT; } + /** Returns {@code true} if the transition is closing mode. */ + public static boolean isClosingMode(@TransitionInfo.TransitionMode int mode) { + return mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK; + } + /** Returns {@code true} if the transition has a display change. */ public static boolean hasDisplayChange(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { 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..3ded7d246499 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -175,6 +175,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements .setName("home_task_overlay_container") .setContainerLayer() .setHidden(false) + .setCallsite("ShellTaskOrganizer.mHomeTaskOverlayContainer") .build(); /** @@ -552,10 +553,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..fac9bf6e2a4b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -344,7 +344,7 @@ public class BubbleStackView extends FrameLayout pw.println("Expanded bubble state:"); pw.println(" expandedBubbleKey: " + mExpandedBubble.getKey()); - final BubbleExpandedView expandedView = mExpandedBubble.getExpandedView(); + final BubbleExpandedView expandedView = getExpandedView(); if (expandedView != null) { pw.println(" expandedViewVis: " + expandedView.getVisibility()); @@ -815,10 +815,11 @@ public class BubbleStackView extends FrameLayout private float getScrimAlphaForDrag(float dragAmount) { // dragAmount should be negative as we allow scroll up only - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null) { float alphaRange = BUBBLE_EXPANDED_SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG; - int dragMax = mExpandedBubble.getExpandedView().getContentHeight(); + int dragMax = expandedView.getContentHeight(); float dragFraction = dragAmount / dragMax; return Math.max(BUBBLE_EXPANDED_SCRIM_ALPHA - alphaRange * dragFraction, @@ -1120,33 +1121,35 @@ public class BubbleStackView extends FrameLayout mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null) { // We need to be Z ordered on top in order for alpha animations to work. - mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true); - mExpandedBubble.getExpandedView().setAnimating(true); + expandedView.setSurfaceZOrderedOnTop(true); + expandedView.setAnimating(true); mExpandedViewContainer.setVisibility(VISIBLE); } } @Override public void onAnimationEnd(Animator animation) { - if (mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null // The surface needs to be Z ordered on top for alpha values to work on the // TaskView, and if we're temporarily hidden, we are still on the screen // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha // = 0f remains in effect. && !mExpandedViewTemporarilyHidden) { - mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); - mExpandedBubble.getExpandedView().setAnimating(false); + expandedView.setSurfaceZOrderedOnTop(false); + expandedView.setAnimating(false); } } }); mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> { - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null) { float alpha = (float) valueAnimator.getAnimatedValue(); - mExpandedBubble.getExpandedView().setContentAlpha(alpha); - mExpandedBubble.getExpandedView().setBackgroundAlpha(alpha); + expandedView.setContentAlpha(alpha); + expandedView.setBackgroundAlpha(alpha); } }); @@ -1390,7 +1393,7 @@ public class BubbleStackView extends FrameLayout } final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION); final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext)) - && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null; + && getExpandedView() != null; ProtoLog.d(WM_SHELL_BUBBLES, "Show manage edu=%b", shouldShow); if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { Log.w(TAG, "Want to show manage edu, but it is forced hidden"); @@ -1417,9 +1420,9 @@ public class BubbleStackView extends FrameLayout * Show manage education if was not showing before. */ private void showManageEdu() { - if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) return; - mManageEduView.show(mExpandedBubble.getExpandedView(), - mStackAnimationController.isStackOnLeftSide()); + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView == null) return; + mManageEduView.show(expandedView, mStackAnimationController.isStackOnLeftSide()); } @VisibleForTesting @@ -1931,6 +1934,11 @@ public class BubbleStackView extends FrameLayout return mExpandedBubble; } + @Nullable + private BubbleExpandedView getExpandedView() { + return mExpandedBubble != null ? mExpandedBubble.getExpandedView() : null; + } + // via BubbleData.Listener @SuppressLint("ClickableViewAccessibility") void addBubble(Bubble bubble) { @@ -2110,13 +2118,11 @@ public class BubbleStackView extends FrameLayout // If we're expanded, screenshot the currently expanded bubble (before expanding the newly // selected bubble) so we can animate it out. - if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null - && !mExpandedViewTemporarilyHidden) { - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - // Before screenshotting, have the real TaskView show on top of other surfaces - // so that the screenshot doesn't flicker on top of it. - mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true); - } + BubbleExpandedView expandedView = getExpandedView(); + if (mIsExpanded && expandedView != null && !mExpandedViewTemporarilyHidden) { + // Before screenshotting, have the real TaskView show on top of other surfaces + // so that the screenshot doesn't flicker on top of it. + expandedView.setSurfaceZOrderedOnTop(true); try { screenshotAnimatingOutBubbleIntoSurface((success) -> { @@ -2136,7 +2142,7 @@ public class BubbleStackView extends FrameLayout private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) { final BubbleViewProvider previouslySelected = mExpandedBubble; mExpandedBubble = bubbleToSelect; - mExpandedViewAnimationController.setExpandedView(mExpandedBubble.getExpandedView()); + mExpandedViewAnimationController.setExpandedView(getExpandedView()); if (mIsExpanded) { hideCurrentInputMethod(); @@ -2445,8 +2451,7 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.animate().translationX(0).start(); } mExpandedAnimationController.expandFromStack(() -> { - if (mIsExpanded && mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null) { + if (mIsExpanded && getExpandedView() != null) { maybeShowManageEdu(); } updateOverflowDotVisibility(true /* expanding */); @@ -2509,13 +2514,14 @@ public class BubbleStackView extends FrameLayout } mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().setContentAlpha(0f); - mExpandedBubble.getExpandedView().setBackgroundAlpha(0f); + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null) { + expandedView.setContentAlpha(0f); + expandedView.setBackgroundAlpha(0f); // We'll be starting the alpha animation after a slight delay, so set this flag early // here. - mExpandedBubble.getExpandedView().setAnimating(true); + expandedView.setAnimating(true); } mDelayedAnimation = () -> { @@ -2545,10 +2551,9 @@ public class BubbleStackView extends FrameLayout .withEndActions(() -> { mExpandedViewContainer.setAnimationMatrix(null); afterExpandedViewAnimation(); - if (mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView() - .setSurfaceZOrderedOnTop(false); + BubbleExpandedView expView = getExpandedView(); + if (expView != null) { + expView.setSurfaceZOrderedOnTop(false); } }) .start(); @@ -2613,12 +2618,13 @@ public class BubbleStackView extends FrameLayout }; mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after, collapsePosition); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null) { // When the animation completes, we should no longer be showing the content. // This won't actually update content visibility immediately, if we are currently // animating. But updates the internal state for the content to be hidden after // animation completes. - mExpandedBubble.getExpandedView().setContentVisibility(false); + expandedView.setContentVisibility(false); } } @@ -2710,10 +2716,10 @@ public class BubbleStackView extends FrameLayout // expanded view animation might not actually set the z ordering for the // expanded view correctly, because the view may still be temporarily // hidden. So set it again here. - BubbleExpandedView bev = mExpandedBubble.getExpandedView(); - if (bev != null) { - mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); - mExpandedBubble.getExpandedView().setAnimating(false); + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null) { + expandedView.setSurfaceZOrderedOnTop(false); + expandedView.setAnimating(false); } }) .start(); @@ -2785,13 +2791,13 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded) { mExpandedViewAnimationController.animateForImeVisibilityChange(visible); - if (mPositioner.showBubblesVertically() - && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + BubbleExpandedView expandedView = getExpandedView(); + if (mPositioner.showBubblesVertically() && expandedView != null) { float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex, getState()).y; float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY); - mExpandedBubble.getExpandedView().setImeVisible(visible); - if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) { + expandedView.setImeVisible(visible); + if (!expandedView.isUsingMaxHeight()) { mExpandedViewContainer.animate().translationY(newExpandedViewTop); } List<Animator> animList = new ArrayList<>(); @@ -3148,7 +3154,8 @@ public class BubbleStackView extends FrameLayout // This should not happen, since the manage menu is only visible when there's an expanded // bubble. If we end up in this state, just hide the menu immediately. - if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView == null) { mManageMenu.setVisibility(View.INVISIBLE); mManageMenuScrim.setVisibility(INVISIBLE); mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */); @@ -3194,8 +3201,8 @@ public class BubbleStackView extends FrameLayout } } - if (mExpandedBubble.getExpandedView().getTaskView() != null) { - mExpandedBubble.getExpandedView().getTaskView().setObscuredTouchRect(mShowingManage + if (expandedView.getTaskView() != null) { + expandedView.getTaskView().setObscuredTouchRect(mShowingManage ? new Rect(0, 0, getWidth(), getHeight()) : null); } @@ -3205,8 +3212,8 @@ public class BubbleStackView extends FrameLayout // When the menu is open, it should be at these coordinates. The menu pops out to the right // in LTR and to the left in RTL. - mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); - final float margin = mExpandedBubble.getExpandedView().getManageButtonMargin(); + expandedView.getManageButtonBoundsOnScreen(mTempRect); + final float margin = expandedView.getManageButtonMargin(); final float targetX = isLtr ? mTempRect.left - margin : mTempRect.right + margin - mManageMenu.getWidth(); @@ -3230,9 +3237,10 @@ public class BubbleStackView extends FrameLayout .withEndActions(() -> { View child = mManageMenu.getChildAt(0); child.requestAccessibilityFocus(); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + BubbleExpandedView expView = getExpandedView(); + if (expView != null) { // Update the AV's obscured touchable region for the new state. - mExpandedBubble.getExpandedView().updateObscuredTouchableRegion(); + expView.updateObscuredTouchableRegion(); } }) .start(); @@ -3247,9 +3255,10 @@ public class BubbleStackView extends FrameLayout .spring(DynamicAnimation.TRANSLATION_Y, targetY + menuHeight / 4f) .withEndActions(() -> { mManageMenu.setVisibility(View.INVISIBLE); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + BubbleExpandedView expView = getExpandedView(); + if (expView != null) { // Update the AV's obscured touchable region for the new state. - mExpandedBubble.getExpandedView().updateObscuredTouchableRegion(); + expView.updateObscuredTouchableRegion(); } }) .start(); @@ -3276,9 +3285,8 @@ public class BubbleStackView extends FrameLayout private void updateExpandedBubble() { mExpandedViewContainer.removeAllViews(); - if (mIsExpanded && mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null) { - BubbleExpandedView bev = mExpandedBubble.getExpandedView(); + BubbleExpandedView bev = getExpandedView(); + if (mIsExpanded && bev != null) { bev.setContentVisibility(false); bev.setAnimating(!mIsExpansionAnimating); mExpandedViewContainerMatrix.setScaleX(0f); @@ -3306,9 +3314,8 @@ public class BubbleStackView extends FrameLayout } private void updateManageButtonListener() { - if (mIsExpanded && mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null) { - BubbleExpandedView bev = mExpandedBubble.getExpandedView(); + BubbleExpandedView bev = getExpandedView(); + if (mIsExpanded && bev != null) { bev.setManageClickListener((view) -> { showManageMenu(true /* show */); }); @@ -3325,14 +3332,13 @@ public class BubbleStackView extends FrameLayout * expanded bubble. */ private void screenshotAnimatingOutBubbleIntoSurface(Consumer<Boolean> onComplete) { - if (!mIsExpanded || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { + final BubbleExpandedView animatingOutExpandedView = getExpandedView(); + if (!mIsExpanded || animatingOutExpandedView == null) { // You can't animate null. onComplete.accept(false); return; } - final BubbleExpandedView animatingOutExpandedView = mExpandedBubble.getExpandedView(); - // Release the previous screenshot if it hasn't been released already. if (mAnimatingOutBubbleBuffer != null) { releaseAnimatingOutBubbleBuffer(); @@ -3364,8 +3370,7 @@ public class BubbleStackView extends FrameLayout mAnimatingOutSurfaceContainer.setTranslationX(translationX); mAnimatingOutSurfaceContainer.setTranslationY(0); - final int[] taskViewLocation = - mExpandedBubble.getExpandedView().getTaskViewLocationOnScreen(); + final int[] taskViewLocation = animatingOutExpandedView.getTaskViewLocationOnScreen(); final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen(); // Translate the surface to overlap the real TaskView. @@ -3427,15 +3432,15 @@ public class BubbleStackView extends FrameLayout int[] paddings = mPositioner.getExpandedViewContainerPadding( mStackAnimationController.isStackOnLeftSide(), isOverflowExpanded); mExpandedViewContainer.setPadding(paddings[0], paddings[1], paddings[2], paddings[3]); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + BubbleExpandedView expandedView = getExpandedView(); + if (expandedView != null) { PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble), getState()); mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble, mPositioner.showBubblesVertically() ? p.y : p.x)); mExpandedViewContainer.setTranslationX(0f); - mExpandedBubble.getExpandedView().updateTaskViewContentWidth(); - mExpandedBubble.getExpandedView().updateView( - mExpandedViewContainer.getLocationOnScreen()); + expandedView.updateTaskViewContentWidth(); + expandedView.updateView(mExpandedViewContainer.getLocationOnScreen()); updatePointerPosition(false /* forIme */); } @@ -3508,7 +3513,8 @@ public class BubbleStackView extends FrameLayout * the pointer is animated to the location. */ private void updatePointerPosition(boolean forIme) { - if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { + BubbleExpandedView expandedView = getExpandedView(); + if (mExpandedBubble == null || expandedView == null) { return; } int index = getBubbleIndex(mExpandedBubble); @@ -3519,7 +3525,7 @@ public class BubbleStackView extends FrameLayout float bubblePosition = mPositioner.showBubblesVertically() ? position.y : position.x; - mExpandedBubble.getExpandedView().setPointerPosition(bubblePosition, + expandedView.setPointerPosition(bubblePosition, mStackOnLeftOrWillBe, forIme /* animate */); } @@ -3541,7 +3547,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/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index fb0a1ab3062e..12bbd51b968d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -522,14 +522,16 @@ public abstract class WMShellModule { RecentsTransitionHandler recentsTransitionHandler, MultiInstanceHelper multiInstanceHelper, @ShellMainThread ShellExecutor mainExecutor, - Optional<DesktopTasksLimiter> desktopTasksLimiter) { + Optional<DesktopTasksLimiter> desktopTasksLimiter, + Optional<RecentTasksController> recentTasksController) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, - recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter); + recentsTransitionHandler, multiInstanceHelper, + mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null)); } @WMSingleton 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..ed0d2b87b03f 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; @@ -223,6 +223,7 @@ public class DesktopModeVisualIndicator { mLeash = builder .setName("Desktop Mode Visual Indicator") .setContainerLayer() + .setCallsite("DesktopModeVisualIndicator.createView") .build(); t.show(mLeash); final WindowManager.LayoutParams lp = @@ -315,10 +316,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 +367,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 +471,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..ef384c74cb5e 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 @@ -70,6 +70,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksLi import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.DesktopModeStatus @@ -97,69 +98,75 @@ 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>, + private val recentTasksController: RecentTasksController? +) : + 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 +185,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 +235,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 +257,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 +282,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,35 +292,57 @@ 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 + moveToDesktop(it, wct) + } ?: moveToDesktopFromNonRunningTask(taskId, wct) return true } + private fun moveToDesktopFromNonRunningTask( + taskId: Int, + wct: WindowContainerTransaction + ): Boolean { + recentTasksController?.findTaskInBackground(taskId)?.let { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d", + taskId + ) + // TODO(342378842): Instead of using default display, support multiple displays + val taskToMinimize = + bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId) + addMoveToDesktopChangesNonRunningTask(wct, taskId) + // TODO(343149901): Add DPI changes for task launch + val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct) + addPendingMinimizeTransition(transition, taskToMinimize) + return true + } ?: return false + } + + private fun addMoveToDesktopChangesNonRunningTask( + wct: WindowContainerTransaction, + taskId: Int + ) { + val options = ActivityOptions.makeBasic() + options.launchWindowingMode = WINDOWING_MODE_FREEFORM + wct.startTask(taskId, options.toBundle()) + } + /** * 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" - ) - return - } if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) { 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 +354,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 +366,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 +390,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 +406,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 +420,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 +441,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 +472,7 @@ class DesktopTasksController( splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_DESKTOP_MODE ) - splitScreenController.transitionHandler - ?.onSplitToDesktop() + splitScreenController.transitionHandler?.onSplitToDesktop() } } @@ -469,16 +491,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 +539,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 +555,7 @@ class DesktopTasksController( KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d", - taskId, + taskId, task.displayId ) @@ -560,7 +582,7 @@ class DesktopTasksController( KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d", - task.taskId, + task.taskId, displayId ) @@ -585,9 +607,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 +622,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 +672,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 +719,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 +744,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 +776,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 +847,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 +865,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 +875,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 +910,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 +926,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 +964,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 +989,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 +1013,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 +1036,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 +1054,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 +1067,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 +1108,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 +1138,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 +1164,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 +1199,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 +1219,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 +1232,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 +1249,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 +1272,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 +1298,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 +1323,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 +1358,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 +1367,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 +1376,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 +1394,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 +1432,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 +1469,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/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index c7f693d8de50..b52b0d8dee74 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -489,6 +489,14 @@ public class PipTransition extends PipTransitionController { // activity windowing mode, and set the task bounds to the final bounds wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); wct.setBounds(taskInfo.token, destinationBounds); + // If the animation is only used to apply destination bounds immediately and + // invisibly, then reshow it until the pip is drawn with the bounds. + final PipAnimationController.PipTransitionAnimator<?> animator = + mPipAnimationController.getCurrentAnimator(); + if (animator != null && animator.getEndValue().equals(0f)) { + tx.addTransactionCommittedListener(mTransitions.getMainExecutor(), + () -> fadeExistingPip(true /* show */)); + } } else { wct.setBounds(taskInfo.token, null /* bounds */); } @@ -675,6 +683,7 @@ public class PipTransition extends PipTransitionController { .setContainerLayer() .setHidden(false) .setParent(root.getLeash()) + .setCallsite("PipTransition.startExitAnimation") .build(); startTransaction.reparent(activitySurface, pipLeash); // Put the activity at local position with offset in case it is letterboxed. @@ -1025,6 +1034,7 @@ public class PipTransition extends PipTransitionController { } startTransaction.apply(); + int animationDuration = mEnterExitAnimationDuration; PipAnimationController.PipTransitionAnimator animator; if (enterAnimationType == ANIM_TYPE_BOUNDS) { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, @@ -1056,8 +1066,17 @@ public class PipTransition extends PipTransitionController { } } } else if (enterAnimationType == ANIM_TYPE_ALPHA) { + // In case augmentRequest() is unable to apply the entering bounds (e.g. the request + // info only contains display change), keep the animation invisible (alpha 0) and + // duration 0 to apply the destination bounds. The actual fade-in animation will be + // done in onFinishResize() after the bounds are applied. + final boolean fadeInAfterOnFinishResize = rotationDelta != Surface.ROTATION_0 + && mFixedRotationState == FIXED_ROTATION_CALLBACK; animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, - 0f, 1f); + 0f, fadeInAfterOnFinishResize ? 0f : 1f); + if (fadeInAfterOnFinishResize) { + animationDuration = 0; + } mSurfaceTransactionHelper .crop(finishTransaction, leash, destinationBounds) .round(finishTransaction, leash, true /* applyCornerRadius */); @@ -1067,7 +1086,7 @@ public class PipTransition extends PipTransitionController { mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) - .setDuration(mEnterExitAnimationDuration); + .setDuration(animationDuration); if (rotationDelta != Surface.ROTATION_0 && mFixedRotationState == FIXED_ROTATION_TRANSITION) { // For fixed rotation, the animation destination bounds is in old rotation coordinates. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index c2f4d72a1ddf..ca0d61f8fc9b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -233,6 +233,7 @@ public class TvPipTransition extends PipTransitionController { .setContainerLayer() .setHidden(false) .setParent(root.getLeash()) + .setCallsite("TvPipTransition.startAnimation") .build(); startTransaction.reparent(activitySurface, pipLeash); // Put the activity at local position with offset in case it is letterboxed. 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..863202d5e1c3 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); } @@ -442,6 +446,25 @@ public class RecentTasksController implements TaskStackListenerCallback, return null; } + /** + * Find the background task that match the given taskId. + */ + @Nullable + public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) { + List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, + ActivityManager.getCurrentUser()); + for (int i = 0; i < tasks.size(); i++) { + final ActivityManager.RecentTaskInfo task = tasks.get(i); + if (task.isVisible) { + continue; + } + if (taskId == task.taskId) { + return task; + } + } + return null; + } public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index 11aa402aa283..a126cbe41b00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.graphics.Rect; +import android.gui.TrustedOverlay; import android.os.Binder; import android.util.CloseGuard; import android.util.Slog; @@ -448,6 +449,14 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mSurfaceCreated = true; mIsInitialized = true; mSurfaceControl = surfaceControl; + // SurfaceControl is expected to be null only in the case of unit tests. Guard against it + // to avoid runtime exception in SurfaceControl.Transaction. + if (surfaceControl != null) { + // TaskView is meant to contain app activities which shouldn't have trusted overlays + // flag set even when itself reparented in a window which is trusted. + mTransaction.setTrustedOverlay(surfaceControl, TrustedOverlay.DISABLED) + .apply(); + } notifyInitialized(); mShellExecutor.execute(() -> { if (mTaskToken == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index 9ce22094d56b..e196254628d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -167,7 +167,7 @@ class ScreenRotationAnimation { t.show(mScreenshotLayer); if (!isCustomRotate()) { mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, - screenshotBuffer.getColorSpace()); + screenshotBuffer.getColorSpace(), mSurfaceControl); } hardwareBuffer.close(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 1be85d05c16e..ad4f02d13cc6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -280,6 +280,7 @@ public class TransitionAnimationHelper { .setParent(rootLeash) .setColorLayer() .setOpaque(true) + .setCallsite("TransitionAnimationHelper.addBackgroundToTransition") .build(); startTransaction .setLayer(animationBackgroundSurface, Integer.MIN_VALUE) 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/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index e85cb6400000..95e0d79c212e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -255,6 +255,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final WindowContainerToken mTaskToken; private final DragPositioningCallback mDragPositioningCallback; private final DragDetector mDragDetector; + private final int mDisplayId; private int mDragPointerId = -1; private boolean mIsDragging; @@ -266,6 +267,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mTaskToken = taskInfo.token; mDragPositioningCallback = dragPositioningCallback; mDragDetector = new DragDetector(this); + mDisplayId = taskInfo.displayId; } @Override @@ -274,7 +276,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (id == R.id.close_window) { mTaskOperations.closeTask(mTaskToken); } else if (id == R.id.back_button) { - mTaskOperations.injectBackKey(); + mTaskOperations.injectBackKey(mDisplayId); } else if (id == R.id.minimize_window) { mTaskOperations.minimizeTask(mTaskToken); } else if (id == R.id.maximize_window) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 9afb057ffbe5..37cdbb47bfe8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -278,11 +278,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) { if (visible && stage != STAGE_TYPE_UNDEFINED) { DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId); - if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext) - || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { - return; + if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) { + mDesktopTasksController.moveToSplit(decor.mTaskInfo); } - mDesktopTasksController.moveToSplit(decor.mTaskInfo); } } }); @@ -388,6 +386,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final DragPositioningCallback mDragPositioningCallback; private final DragDetector mDragDetector; private final GestureDetector mGestureDetector; + private final int mDisplayId; /** * Whether to pilfer the next motion event to send cancellations to the windows below. @@ -409,6 +408,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragPositioningCallback = dragPositioningCallback; mDragDetector = new DragDetector(this); mGestureDetector = new GestureDetector(mContext, this); + mDisplayId = taskInfo.displayId; mCloseMaximizeWindowRunnable = () -> { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); if (decoration == null) return; @@ -434,7 +434,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskOperations.closeTask(mTaskToken, wct); } } else if (id == R.id.back_button) { - mTaskOperations.injectBackKey(); + mTaskOperations.injectBackKey(mDisplayId); } else if (id == R.id.caption_handle || id == R.id.open_menu_button) { if (!decoration.isHandleMenuActive()) { moveTaskToFront(decoration.mTaskInfo); @@ -583,17 +583,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (ev.getAction() == ACTION_HOVER_MOVE && MaximizeMenu.Companion.isMaximizeMenuView(id)) { decoration.onMaximizeMenuHoverMove(id, ev); + mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable); } else if (ev.getAction() == ACTION_HOVER_EXIT) { if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) { decoration.onMaximizeWindowHoverExit(); - } else if (id == R.id.maximize_window || id == R.id.maximize_menu) { + } else if (id == R.id.maximize_window + || MaximizeMenu.Companion.isMaximizeMenuView(id)) { // Close menu if not hovering over maximize menu or maximize button after a // delay to give user a chance to re-enter view or to move from one maximize // menu view to another. mMainHandler.postDelayed(mCloseMaximizeWindowRunnable, CLOSE_MAXIMIZE_MENU_DELAY_MS); - } else if (MaximizeMenu.Companion.isMaximizeMenuView(id)) { - decoration.onMaximizeMenuHoverExit(id, ev); + if (id != R.id.maximize_window) { + decoration.onMaximizeMenuHoverExit(id, ev); + } } return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 5379ca6cd51d..badce6e93d67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -132,6 +132,7 @@ class DragResizeInputListener implements AutoCloseable { .setName("TaskInputSink of " + decorationSurface) .setContainerLayer() .setParent(mDecorationSurface) + .setCallsite("DragResizeInputListener.constructor") .build(); mSurfaceControlTransactionSupplier.get() .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER) 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/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java deleted file mode 100644 index 93e2a21c6b02..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ /dev/null @@ -1,451 +0,0 @@ -/* - * Copyright (C) 2023 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.wm.shell.windowdecor; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.ColorRes; -import android.annotation.NonNull; -import android.app.ActivityManager.RunningTaskInfo; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Trace; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.SurfaceSession; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowlessWindowManager; -import android.widget.ImageView; -import android.window.TaskConstants; - -import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayController; - -import java.util.function.Supplier; - -/** - * Creates and updates a veil that covers task contents on resize. - */ -public class ResizeVeil { - private static final String TAG = "ResizeVeil"; - private static final int RESIZE_ALPHA_DURATION = 100; - - private static final int VEIL_CONTAINER_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL; - /** The background is a child of the veil container layer and goes at the bottom. */ - private static final int VEIL_BACKGROUND_LAYER = 0; - /** The icon is a child of the veil container layer and goes in front of the background. */ - private static final int VEIL_ICON_LAYER = 1; - - private final Context mContext; - private final DisplayController mDisplayController; - private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; - private final SurfaceControlBuilderFactory mSurfaceControlBuilderFactory; - private final WindowDecoration.SurfaceControlViewHostFactory mSurfaceControlViewHostFactory; - private final SurfaceSession mSurfaceSession = new SurfaceSession(); - private final Bitmap mAppIcon; - private ImageView mIconView; - private int mIconSize; - private SurfaceControl mParentSurface; - - /** A container surface to host the veil background and icon child surfaces. */ - private SurfaceControl mVeilSurface; - /** A color surface for the veil background. */ - private SurfaceControl mBackgroundSurface; - /** A surface that hosts a windowless window with the app icon. */ - private SurfaceControl mIconSurface; - - private final RunningTaskInfo mTaskInfo; - private SurfaceControlViewHost mViewHost; - private Display mDisplay; - private ValueAnimator mVeilAnimator; - - private boolean mIsShowing = false; - - private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = - new DisplayController.OnDisplaysChangedListener() { - @Override - public void onDisplayAdded(int displayId) { - if (mTaskInfo.displayId != displayId) { - return; - } - mDisplayController.removeDisplayWindowListener(this); - setupResizeVeil(); - } - }; - - public ResizeVeil(Context context, - @NonNull DisplayController displayController, - Bitmap appIcon, RunningTaskInfo taskInfo, - SurfaceControl taskSurface, - Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { - this(context, - displayController, - appIcon, - taskInfo, - taskSurface, - surfaceControlTransactionSupplier, - new SurfaceControlBuilderFactory() {}, - new WindowDecoration.SurfaceControlViewHostFactory() {}); - } - - public ResizeVeil(Context context, - @NonNull DisplayController displayController, - Bitmap appIcon, RunningTaskInfo taskInfo, - SurfaceControl taskSurface, - Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, - SurfaceControlBuilderFactory surfaceControlBuilderFactory, - WindowDecoration.SurfaceControlViewHostFactory surfaceControlViewHostFactory) { - mContext = context; - mDisplayController = displayController; - mAppIcon = appIcon; - mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; - mTaskInfo = taskInfo; - mParentSurface = taskSurface; - mSurfaceControlBuilderFactory = surfaceControlBuilderFactory; - mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; - setupResizeVeil(); - } - /** - * Create the veil in its default invisible state. - */ - private void setupResizeVeil() { - if (!obtainDisplayOrRegisterListener()) { - // Display may not be available yet, skip this until then. - return; - } - Trace.beginSection("ResizeVeil#setupResizeVeil"); - mVeilSurface = mSurfaceControlBuilderFactory - .create("Resize veil of Task=" + mTaskInfo.taskId) - .setContainerLayer() - .setHidden(true) - .setParent(mParentSurface) - .setCallsite("ResizeVeil#setupResizeVeil") - .build(); - mBackgroundSurface = mSurfaceControlBuilderFactory - .create("Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession) - .setColorLayer() - .setHidden(true) - .setParent(mVeilSurface) - .setCallsite("ResizeVeil#setupResizeVeil") - .build(); - mIconSurface = mSurfaceControlBuilderFactory - .create("Resize veil icon of Task=" + mTaskInfo.taskId) - .setContainerLayer() - .setHidden(true) - .setParent(mVeilSurface) - .setCallsite("ResizeVeil#setupResizeVeil") - .build(); - - mIconSize = mContext.getResources() - .getDimensionPixelSize(R.dimen.desktop_mode_resize_veil_icon_size); - final View root = LayoutInflater.from(mContext) - .inflate(R.layout.desktop_mode_resize_veil, null /* root */); - mIconView = root.findViewById(R.id.veil_application_icon); - mIconView.setImageBitmap(mAppIcon); - - final WindowManager.LayoutParams lp = - new WindowManager.LayoutParams( - mIconSize, - mIconSize, - WindowManager.LayoutParams.TYPE_APPLICATION, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSPARENT); - lp.setTitle("Resize veil icon window of Task=" + mTaskInfo.taskId); - lp.setTrustedOverlay(); - - final WindowlessWindowManager wwm = new WindowlessWindowManager(mTaskInfo.configuration, - mIconSurface, null /* hostInputToken */); - - mViewHost = mSurfaceControlViewHostFactory.create(mContext, mDisplay, wwm, "ResizeVeil"); - mViewHost.setView(root, lp); - Trace.endSection(); - } - - private boolean obtainDisplayOrRegisterListener() { - mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); - if (mDisplay == null) { - mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener); - return false; - } - return true; - } - - /** - * Shows the veil surface/view. - * - * @param t the transaction to apply in sync with the veil draw - * @param parentSurface the surface that the veil should be a child of - * @param taskBounds the bounds of the task that owns the veil - * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown - * immediately - */ - public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface, - Rect taskBounds, boolean fadeIn) { - if (!isReady() || isVisible()) { - t.apply(); - return; - } - mIsShowing = true; - - // Parent surface can change, ensure it is up to date. - if (!parentSurface.equals(mParentSurface)) { - t.reparent(mVeilSurface, parentSurface); - mParentSurface = parentSurface; - } - - t.show(mVeilSurface); - t.setLayer(mVeilSurface, VEIL_CONTAINER_LAYER); - t.setLayer(mIconSurface, VEIL_ICON_LAYER); - t.setLayer(mBackgroundSurface, VEIL_BACKGROUND_LAYER); - t.setColor(mBackgroundSurface, - Color.valueOf(mContext.getColor(getBackgroundColorId())).getComponents()); - - relayout(taskBounds, t); - if (fadeIn) { - cancelAnimation(); - final SurfaceControl.Transaction veilAnimT = mSurfaceControlTransactionSupplier.get(); - mVeilAnimator = new ValueAnimator(); - mVeilAnimator.setFloatValues(0f, 1f); - mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION); - mVeilAnimator.addUpdateListener(animation -> { - veilAnimT.setAlpha(mBackgroundSurface, mVeilAnimator.getAnimatedFraction()); - veilAnimT.apply(); - }); - mVeilAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - veilAnimT.show(mBackgroundSurface) - .setAlpha(mBackgroundSurface, 0) - .apply(); - } - - @Override - public void onAnimationEnd(Animator animation) { - veilAnimT.setAlpha(mBackgroundSurface, 1).apply(); - } - }); - - final SurfaceControl.Transaction iconAnimT = mSurfaceControlTransactionSupplier.get(); - final ValueAnimator iconAnimator = new ValueAnimator(); - iconAnimator.setFloatValues(0f, 1f); - iconAnimator.setDuration(RESIZE_ALPHA_DURATION); - iconAnimator.addUpdateListener(animation -> { - iconAnimT.setAlpha(mIconSurface, animation.getAnimatedFraction()); - iconAnimT.apply(); - }); - iconAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - iconAnimT.show(mIconSurface) - .setAlpha(mIconSurface, 0) - .apply(); - } - - @Override - public void onAnimationEnd(Animator animation) { - iconAnimT.setAlpha(mIconSurface, 1).apply(); - } - }); - // Let the animators show it with the correct alpha value once the animation starts. - t.hide(mIconSurface); - t.hide(mBackgroundSurface); - t.apply(); - - mVeilAnimator.start(); - iconAnimator.start(); - } else { - // Show the veil immediately. - t.show(mIconSurface); - t.show(mBackgroundSurface); - t.setAlpha(mIconSurface, 1); - t.setAlpha(mBackgroundSurface, 1); - t.apply(); - } - } - - /** - * Animate veil's alpha to 1, fading it in. - */ - public void showVeil(SurfaceControl parentSurface, Rect taskBounds) { - if (!isReady() || isVisible()) { - return; - } - SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); - showVeil(t, parentSurface, taskBounds, true /* fadeIn */); - } - - /** - * Update veil bounds to match bounds changes. - * @param newBounds bounds to update veil to. - */ - private void relayout(Rect newBounds, SurfaceControl.Transaction t) { - t.setWindowCrop(mVeilSurface, newBounds.width(), newBounds.height()); - final PointF iconPosition = calculateAppIconPosition(newBounds); - t.setPosition(mIconSurface, iconPosition.x, iconPosition.y); - t.setPosition(mParentSurface, newBounds.left, newBounds.top); - t.setWindowCrop(mParentSurface, newBounds.width(), newBounds.height()); - } - - /** - * Calls relayout to update task and veil bounds. - * @param newBounds bounds to update veil to. - */ - public void updateResizeVeil(Rect newBounds) { - if (!isVisible()) { - return; - } - SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); - updateResizeVeil(t, newBounds); - } - - /** - * Calls relayout to update task and veil bounds. - * Finishes veil fade in if animation is currently running; this is to prevent empty space - * being visible behind the transparent veil during a fast resize. - * - * @param t a transaction to be applied in sync with the veil draw. - * @param newBounds bounds to update veil to. - */ - public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) { - if (!isVisible()) { - t.apply(); - return; - } - if (mVeilAnimator != null && mVeilAnimator.isStarted()) { - mVeilAnimator.removeAllUpdateListeners(); - mVeilAnimator.end(); - } - relayout(newBounds, t); - t.apply(); - } - - /** - * Animate veil's alpha to 0, fading it out. - */ - public void hideVeil() { - if (!isVisible()) { - return; - } - cancelAnimation(); - mVeilAnimator = new ValueAnimator(); - mVeilAnimator.setFloatValues(1, 0); - mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION); - mVeilAnimator.addUpdateListener(animation -> { - SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); - t.setAlpha(mBackgroundSurface, 1 - mVeilAnimator.getAnimatedFraction()); - t.setAlpha(mIconSurface, 1 - mVeilAnimator.getAnimatedFraction()); - t.apply(); - }); - mVeilAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); - t.hide(mBackgroundSurface); - t.hide(mIconSurface); - t.apply(); - } - }); - mVeilAnimator.start(); - mIsShowing = false; - } - - @ColorRes - private int getBackgroundColorId() { - Configuration configuration = mContext.getResources().getConfiguration(); - if ((configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES) { - return R.color.desktop_mode_resize_veil_dark; - } else { - return R.color.desktop_mode_resize_veil_light; - } - } - - private PointF calculateAppIconPosition(Rect parentBounds) { - return new PointF((float) parentBounds.width() / 2 - (float) mIconSize / 2, - (float) parentBounds.height() / 2 - (float) mIconSize / 2); - } - - private void cancelAnimation() { - if (mVeilAnimator != null) { - mVeilAnimator.removeAllUpdateListeners(); - mVeilAnimator.cancel(); - } - } - - /** - * Whether the resize veil is currently visible. - * - * Note: when animating a {@link ResizeVeil#hideVeil()}, the veil is considered visible as soon - * as the animation starts. - */ - private boolean isVisible() { - return mIsShowing; - } - - /** Whether the resize veil is ready to be shown. */ - private boolean isReady() { - return mViewHost != null; - } - - /** - * Dispose of veil when it is no longer needed, likely on close of its container decor. - */ - void dispose() { - cancelAnimation(); - mIsShowing = false; - mVeilAnimator = null; - - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); - if (mBackgroundSurface != null) { - t.remove(mBackgroundSurface); - mBackgroundSurface = null; - } - if (mIconSurface != null) { - t.remove(mIconSurface); - mIconSurface = null; - } - if (mVeilSurface != null) { - t.remove(mVeilSurface); - mVeilSurface = null; - } - t.apply(); - mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener); - } - - interface SurfaceControlBuilderFactory { - default SurfaceControl.Builder create(@NonNull String name) { - return new SurfaceControl.Builder().setName(name); - } - default SurfaceControl.Builder create(@NonNull String name, - @NonNull SurfaceSession surfaceSession) { - return new SurfaceControl.Builder(surfaceSession).setName(name); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt new file mode 100644 index 000000000000..4f2d945e49f9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt @@ -0,0 +1,417 @@ +/* + * 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.wm.shell.windowdecor + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.app.ActivityManager.RunningTaskInfo +import android.content.Context +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.PixelFormat +import android.graphics.PointF +import android.graphics.Rect +import android.os.Trace +import android.view.Display +import android.view.LayoutInflater +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.SurfaceSession +import android.view.WindowManager +import android.view.WindowlessWindowManager +import android.widget.ImageView +import android.window.TaskConstants +import com.android.wm.shell.R +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener +import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory +import java.util.function.Supplier + +/** + * Creates and updates a veil that covers task contents on resize. + */ +class ResizeVeil @JvmOverloads constructor( + private val context: Context, + private val displayController: DisplayController, + private val appIcon: Bitmap, + private val taskInfo: RunningTaskInfo, + private var parentSurface: SurfaceControl, + private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>, + private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory = + object : SurfaceControlBuilderFactory {}, + private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = + object : SurfaceControlViewHostFactory {} +) { + private val surfaceSession = SurfaceSession() + private lateinit var iconView: ImageView + private var iconSize = 0 + + /** A container surface to host the veil background and icon child surfaces. */ + private var veilSurface: SurfaceControl? = null + /** A color surface for the veil background. */ + private var backgroundSurface: SurfaceControl? = null + /** A surface that hosts a windowless window with the app icon. */ + private var iconSurface: SurfaceControl? = null + private var viewHost: SurfaceControlViewHost? = null + private var display: Display? = null + private var veilAnimator: ValueAnimator? = null + + /** + * Whether the resize veil is currently visible. + * + * Note: when animating a [ResizeVeil.hideVeil], the veil is considered visible as soon + * as the animation starts. + */ + private var isVisible = false + + private val onDisplaysChangedListener: OnDisplaysChangedListener = + object : OnDisplaysChangedListener { + override fun onDisplayAdded(displayId: Int) { + if (taskInfo.displayId != displayId) { + return + } + displayController.removeDisplayWindowListener(this) + setupResizeVeil() + } + } + + private val backgroundColorId: Int + get() { + val configuration = context.resources.configuration + return if (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + == Configuration.UI_MODE_NIGHT_YES) { + R.color.desktop_mode_resize_veil_dark + } else { + R.color.desktop_mode_resize_veil_light + } + } + + /** + * Whether the resize veil is ready to be shown. + */ + private val isReady: Boolean + get() = viewHost != null + + init { + setupResizeVeil() + } + + /** + * Create the veil in its default invisible state. + */ + private fun setupResizeVeil() { + if (!obtainDisplayOrRegisterListener()) { + // Display may not be available yet, skip this until then. + return + } + Trace.beginSection("ResizeVeil#setupResizeVeil") + veilSurface = surfaceControlBuilderFactory + .create("Resize veil of Task=" + taskInfo.taskId) + .setContainerLayer() + .setHidden(true) + .setParent(parentSurface) + .setCallsite("ResizeVeil#setupResizeVeil") + .build() + backgroundSurface = surfaceControlBuilderFactory + .create("Resize veil background of Task=" + taskInfo.taskId, surfaceSession) + .setColorLayer() + .setHidden(true) + .setParent(veilSurface) + .setCallsite("ResizeVeil#setupResizeVeil") + .build() + iconSurface = surfaceControlBuilderFactory + .create("Resize veil icon of Task=" + taskInfo.taskId) + .setContainerLayer() + .setHidden(true) + .setParent(veilSurface) + .setCallsite("ResizeVeil#setupResizeVeil") + .build() + iconSize = context.resources + .getDimensionPixelSize(R.dimen.desktop_mode_resize_veil_icon_size) + val root = LayoutInflater.from(context) + .inflate(R.layout.desktop_mode_resize_veil, null /* root */) + iconView = root.requireViewById(R.id.veil_application_icon) + iconView.setImageBitmap(appIcon) + val lp = WindowManager.LayoutParams( + iconSize, + iconSize, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSPARENT) + lp.title = "Resize veil icon window of Task=" + taskInfo.taskId + lp.setTrustedOverlay() + val wwm = WindowlessWindowManager(taskInfo.configuration, + iconSurface, null /* hostInputToken */) + viewHost = surfaceControlViewHostFactory.create(context, display, wwm, "ResizeVeil") + viewHost?.setView(root, lp) + Trace.endSection() + } + + private fun obtainDisplayOrRegisterListener(): Boolean { + display = displayController.getDisplay(taskInfo.displayId) + if (display == null) { + displayController.addDisplayWindowListener(onDisplaysChangedListener) + return false + } + return true + } + + /** + * Shows the veil surface/view. + * + * @param t the transaction to apply in sync with the veil draw + * @param parent the surface that the veil should be a child of + * @param taskBounds the bounds of the task that owns the veil + * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown + * immediately + */ + fun showVeil( + t: SurfaceControl.Transaction, + parent: SurfaceControl, + taskBounds: Rect, + fadeIn: Boolean + ) { + if (!isReady || isVisible) { + t.apply() + return + } + isVisible = true + val background = backgroundSurface + val icon = iconSurface + val veil = veilSurface + if (background == null || icon == null || veil == null) return + + // Parent surface can change, ensure it is up to date. + if (parent != parentSurface) { + t.reparent(veil, parent) + parentSurface = parent + } + + + t.show(veil) + .setLayer(veil, VEIL_CONTAINER_LAYER) + .setLayer(icon, VEIL_ICON_LAYER) + .setLayer(background, VEIL_BACKGROUND_LAYER) + .setColor(background, + Color.valueOf(context.getColor(backgroundColorId)).components) + relayout(taskBounds, t) + if (fadeIn) { + cancelAnimation() + val veilAnimT = surfaceControlTransactionSupplier.get() + val iconAnimT = surfaceControlTransactionSupplier.get() + veilAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = RESIZE_ALPHA_DURATION + addUpdateListener { + veilAnimT.setAlpha(background, animatedValue as Float) + .apply() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator) { + veilAnimT.show(background) + .setAlpha(background, 0f) + .apply() + } + + override fun onAnimationEnd(animation: Animator) { + veilAnimT.setAlpha(background, 1f).apply() + } + }) + } + val iconAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = RESIZE_ALPHA_DURATION + addUpdateListener { + iconAnimT.setAlpha(icon, animatedValue as Float) + .apply() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator) { + iconAnimT.show(icon) + .setAlpha(icon, 0f) + .apply() + } + + override fun onAnimationEnd(animation: Animator) { + iconAnimT.setAlpha(icon, 1f).apply() + } + }) + } + + // Let the animators show it with the correct alpha value once the animation starts. + t.hide(icon) + .hide(background) + .apply() + veilAnimator?.start() + iconAnimator.start() + } else { + // Show the veil immediately. + t.show(icon) + .show(background) + .setAlpha(icon, 1f) + .setAlpha(background, 1f) + .apply() + } + } + + /** + * Animate veil's alpha to 1, fading it in. + */ + fun showVeil(parentSurface: SurfaceControl, taskBounds: Rect) { + if (!isReady || isVisible) { + return + } + val t = surfaceControlTransactionSupplier.get() + showVeil(t, parentSurface, taskBounds, true /* fadeIn */) + } + + /** + * Update veil bounds to match bounds changes. + * @param newBounds bounds to update veil to. + */ + private fun relayout(newBounds: Rect, t: SurfaceControl.Transaction) { + val iconPosition = calculateAppIconPosition(newBounds) + val veil = veilSurface + val icon = iconSurface + if (veil == null || icon == null) return + t.setWindowCrop(veil, newBounds.width(), newBounds.height()) + .setPosition(icon, iconPosition.x, iconPosition.y) + .setPosition(parentSurface, newBounds.left.toFloat(), newBounds.top.toFloat()) + .setWindowCrop(parentSurface, newBounds.width(), newBounds.height()) + } + + /** + * Calls relayout to update task and veil bounds. + * @param newBounds bounds to update veil to. + */ + fun updateResizeVeil(newBounds: Rect) { + if (!isVisible) { + return + } + val t = surfaceControlTransactionSupplier.get() + updateResizeVeil(t, newBounds) + } + + /** + * Calls relayout to update task and veil bounds. + * Finishes veil fade in if animation is currently running; this is to prevent empty space + * being visible behind the transparent veil during a fast resize. + * + * @param t a transaction to be applied in sync with the veil draw. + * @param newBounds bounds to update veil to. + */ + fun updateResizeVeil(t: SurfaceControl.Transaction, newBounds: Rect) { + if (!isVisible) { + t.apply() + return + } + veilAnimator?.let { animator -> + if (animator.isStarted) { + animator.removeAllUpdateListeners() + animator.end() + } + } + relayout(newBounds, t) + t.apply() + } + + /** + * Animate veil's alpha to 0, fading it out. + */ + fun hideVeil() { + if (!isVisible) { + return + } + cancelAnimation() + val background = backgroundSurface + val icon = iconSurface + if (background == null || icon == null) return + + veilAnimator = ValueAnimator.ofFloat(1f, 0f).apply { + duration = RESIZE_ALPHA_DURATION + addUpdateListener { + surfaceControlTransactionSupplier.get() + .setAlpha(background, animatedValue as Float) + .setAlpha(icon, animatedValue as Float) + .apply() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + surfaceControlTransactionSupplier.get() + .hide(background) + .hide(icon) + .apply() + } + }) + } + veilAnimator?.start() + isVisible = false + } + + private fun calculateAppIconPosition(parentBounds: Rect): PointF { + return PointF(parentBounds.width().toFloat() / 2 - iconSize.toFloat() / 2, + parentBounds.height().toFloat() / 2 - iconSize.toFloat() / 2) + } + + private fun cancelAnimation() { + veilAnimator?.removeAllUpdateListeners() + veilAnimator?.cancel() + } + + /** + * Dispose of veil when it is no longer needed, likely on close of its container decor. + */ + fun dispose() { + cancelAnimation() + veilAnimator = null + isVisible = false + + viewHost?.release() + viewHost = null + + val t: SurfaceControl.Transaction = surfaceControlTransactionSupplier.get() + backgroundSurface?.let { background -> t.remove(background) } + backgroundSurface = null + iconSurface?.let { icon -> t.remove(icon) } + iconSurface = null + veilSurface?.let { veil -> t.remove(veil) } + veilSurface = null + t.apply() + displayController.removeDisplayWindowListener(onDisplaysChangedListener) + } + + interface SurfaceControlBuilderFactory { + fun create(name: String): SurfaceControl.Builder { + return SurfaceControl.Builder().setName(name) + } + + fun create(name: String, surfaceSession: SurfaceSession): SurfaceControl.Builder { + return SurfaceControl.Builder(surfaceSession).setName(name) + } + } + + companion object { + private const val TAG = "ResizeVeil" + private const val RESIZE_ALPHA_DURATION = 100L + private const val VEIL_CONTAINER_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL + + /** The background is a child of the veil container layer and goes at the bottom. */ + private const val VEIL_BACKGROUND_LAYER = 0 + + /** The icon is a child of the veil container layer and goes in front of the background. */ + private const val VEIL_ICON_LAYER = 1 + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java index 53d4e2701849..ad238c35dd83 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java @@ -52,19 +52,19 @@ class TaskOperations { mSyncQueue = syncQueue; } - void injectBackKey() { - sendBackEvent(KeyEvent.ACTION_DOWN); - sendBackEvent(KeyEvent.ACTION_UP); + void injectBackKey(int displayId) { + sendBackEvent(KeyEvent.ACTION_DOWN, displayId); + sendBackEvent(KeyEvent.ACTION_UP, displayId); } - private void sendBackEvent(int action) { + private void sendBackEvent(int action, int displayId) { final long when = SystemClock.uptimeMillis(); final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); - ev.setDisplayId(mContext.getDisplay().getDisplayId()); + ev.setDisplayId(displayId); if (!mContext.getSystemService(InputManager.class) .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) { Log.e(TAG, "Inject input event fail"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 2cbe47212c63..2ae3cb9ef3c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -51,6 +51,7 @@ import android.window.TaskConstants; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.shared.DesktopModeStatus; @@ -268,6 +269,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setName("Decor container of Task=" + mTaskInfo.taskId) .setContainerLayer() .setParent(mTaskSurface) + .setCallsite("WindowDecoration.relayout_1") .build(); startT.setTrustedOverlay(mDecorationContainerSurface, true) @@ -285,6 +287,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setName("Caption container of Task=" + mTaskInfo.taskId) .setContainerLayer() .setParent(mDecorationContainerSurface) + .setCallsite("WindowDecoration.relayout_2") .build(); } @@ -575,6 +578,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setName(namePrefix + " of Task=" + mTaskInfo.taskId) .setContainerLayer() .setParent(mDecorationContainerSurface) + .setCallsite("WindowDecoration.addWindow") .build(); View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null); @@ -684,7 +688,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } - interface SurfaceControlViewHostFactory { + @VisibleForTesting + public interface SurfaceControlViewHostFactory { default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) { return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration"); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt index ec204714c341..7ade9876d28a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt @@ -23,13 +23,13 @@ import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BA val TaskInfo.isTransparentCaptionBarAppearance: Boolean get() { - val appearance = taskDescription?.systemBarsAppearance ?: 0 + val appearance = taskDescription?.topOpaqueSystemBarsAppearance ?: 0 return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0 } val TaskInfo.isLightCaptionBarAppearance: Boolean get() { - val appearance = taskDescription?.systemBarsAppearance ?: 0 + val appearance = taskDescription?.topOpaqueSystemBarsAppearance ?: 0 return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0 } 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/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt index 285e5b6a04a5..51b291c0b7a4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt @@ -39,7 +39,7 @@ import org.junit.runner.RunWith class DesktopModeUiEventLoggerTest : ShellTestCase() { private lateinit var uiEventLoggerFake: UiEventLoggerFake private lateinit var logger: DesktopModeUiEventLogger - private val instanceIdSequence = InstanceIdSequence(10) + private val instanceIdSequence = InstanceIdSequence(/* instanceIdMax */ 1 shl 20) @Before 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..cf6cea2b34a7 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD @@ -47,13 +48,16 @@ import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DisplayAreaInfo +import android.window.IWindowContainerToken import android.window.RemoteTransition import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER +import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession @@ -78,6 +82,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFulls import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask import com.android.wm.shell.draganddrop.DragAndDropController +import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.DesktopModeStatus @@ -93,6 +98,7 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE import com.android.wm.shell.transition.Transitions.TransitionHandler import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import java.util.Optional import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before @@ -115,7 +121,6 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.capture import org.mockito.quality.Strictness -import java.util.Optional import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.mockito.Mockito.`when` as whenever @@ -154,6 +159,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var multiInstanceHelper: MultiInstanceHelper @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator + @Mock lateinit var recentTasksController: RecentTasksController private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -233,6 +239,7 @@ class DesktopTasksControllerTest : ShellTestCase() { multiInstanceHelper, shellExecutor, Optional.of(desktopTasksLimiter), + recentTasksController ) } @@ -622,7 +629,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(task) val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) + .isEqualTo(WINDOWING_MODE_FREEFORM) } @Test @@ -643,14 +650,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_deviceNotSupported_doesNothing() { - val task = setUpFullscreenTask() + fun moveToDesktop_nonRunningTask_launchesInFreeform() { + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) - // Simulate non compatible device - doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = createTaskInfo(1) - controller.moveToDesktop(task) - verifyWCTNotExecuted() + whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) + + controller.moveToDesktop(task.taskId) + with(getLatestMoveToDesktopWct()){ + assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + } } @Test @@ -666,6 +676,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_deviceNotSupported_doesNothing() { + val task = setUpFullscreenTask() + + // Simulate non compatible device + doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + + controller.moveToDesktop(task) + verifyWCTNotExecuted() + } + + @Test fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() { val task = setUpFullscreenTask() @@ -1614,7 +1635,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 } @@ -1834,6 +1855,20 @@ private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component) } +private fun WindowContainerTransaction.assertLaunchTaskAt( + index: Int, + taskId: Int, + windowingMode: Int +) { + val keyLaunchWindowingMode = "android.activity.windowingMode" + + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK) + assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId) + assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED)) + .isEqualTo(windowingMode) +} private fun WindowContainerTransaction?.anyDensityConfigChange( token: WindowContainerToken ): Boolean { @@ -1841,3 +1876,7 @@ private fun WindowContainerTransaction?.anyDensityConfigChange( change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0) } ?: false } +private fun createTaskInfo(id: Int) = RecentTaskInfo().apply { + taskId = id + token = WindowContainerToken(mock(IWindowContainerToken::class.java)) +} 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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index aa2cee79fcfc..9c1dc22bcef2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -26,6 +26,7 @@ import android.content.Context import android.graphics.Rect import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay +import android.hardware.input.InputManager import android.os.Handler import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsEnabled @@ -42,8 +43,10 @@ import android.view.InputChannel import android.view.InputMonitor import android.view.InsetsSource import android.view.InsetsState +import android.view.KeyEvent import android.view.SurfaceControl import android.view.SurfaceView +import android.view.View import android.view.WindowInsets.Type.navigationBars import android.view.WindowInsets.Type.statusBars import androidx.test.filters.SmallTest @@ -51,6 +54,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.window.flags.Flags +import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase @@ -61,6 +65,7 @@ import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopTasksController +import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.KeyguardChangeListener import com.android.wm.shell.sysui.ShellCommandHandler @@ -70,6 +75,7 @@ import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener import java.util.Optional import java.util.function.Supplier +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test @@ -279,6 +285,41 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + fun testBackEventHasRightDisplayId() { + val secondaryDisplay = createVirtualDisplay() ?: return + val secondaryDisplayId = secondaryDisplay.display.displayId + val task = createTask( + displayId = secondaryDisplayId, + windowingMode = WINDOWING_MODE_FREEFORM + ) + val windowDecor = setUpMockDecorationForTask(task) + + onTaskOpening(task) + val onClickListenerCaptor = argumentCaptor<View.OnClickListener>() + verify(windowDecor).setCaptionListeners( + onClickListenerCaptor.capture(), any(), any(), any()) + + val onClickListener = onClickListenerCaptor.firstValue + val view = mock(View::class.java) + whenever(view.id).thenReturn(R.id.back_button) + + val inputManager = mock(InputManager::class.java) + mContext.addMockSystemService(InputManager::class.java, inputManager) + + val freeformTaskTransitionStarter = mock(FreeformTaskTransitionStarter::class.java) + desktopModeWindowDecorViewModel + .setFreeformTaskTransitionStarter(freeformTaskTransitionStarter) + + onClickListener.onClick(view) + + val eventCaptor = argumentCaptor<KeyEvent>() + verify(inputManager, times(2)).injectInputEvent(eventCaptor.capture(), anyInt()) + + assertEquals(secondaryDisplayId, eventCaptor.firstValue.displayId) + assertEquals(secondaryDisplayId, eventCaptor.secondValue.displayId) + } + + @Test fun testCaptionIsNotCreatedWhenKeyguardIsVisible() { val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) val keyguardListenerCaptor = argumentCaptor<KeyguardChangeListener>() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 3ca9b57e03fd..a731e5394bdf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -228,7 +228,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - taskInfo.taskDescription.setSystemBarsAppearance( + taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance( APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND); final RelayoutParams relayoutParams = new RelayoutParams(); @@ -246,7 +246,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - taskInfo.taskDescription.setSystemBarsAppearance(0); + taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0); final RelayoutParams relayoutParams = new RelayoutParams(); DesktopModeWindowDecoration.updateRelayoutParams( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt index 87425915fbf7..5da57c50e6c1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt @@ -33,6 +33,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Spy import org.mockito.kotlin.any @@ -102,6 +104,14 @@ class ResizeVeilTest : ShellTestCase() { .create("Resize veil icon of Task=" + taskInfo.taskId)) .thenReturn(spyIconSurfaceBuilder) doReturn(mockIconSurface).whenever(spyIconSurfaceBuilder).build() + + doReturn(mockTransaction).whenever(mockTransaction).setLayer(any(), anyInt()) + doReturn(mockTransaction).whenever(mockTransaction).setAlpha(any(), anyFloat()) + doReturn(mockTransaction).whenever(mockTransaction).show(any()) + doReturn(mockTransaction).whenever(mockTransaction).hide(any()) + doReturn(mockTransaction).whenever(mockTransaction) + .setPosition(any(), anyFloat(), anyFloat()) + doReturn(mockTransaction).whenever(mockTransaction).setWindowCrop(any(), anyInt(), anyInt()) } @Test @@ -139,52 +149,48 @@ class ResizeVeilTest : ShellTestCase() { @Test fun showVeil() { val veil = createResizeVeil() - val tx = mock<SurfaceControl.Transaction>() - veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) + veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) - verify(tx).show(mockResizeVeilSurface) - verify(tx).show(mockBackgroundSurface) - verify(tx).show(mockIconSurface) - verify(tx).apply() + verify(mockTransaction).show(mockResizeVeilSurface) + verify(mockTransaction).show(mockBackgroundSurface) + verify(mockTransaction).show(mockIconSurface) + verify(mockTransaction).apply() } @Test fun showVeil_displayUnavailable_doesNotShow() { val veil = createResizeVeil(withDisplayAvailable = false) - val tx = mock<SurfaceControl.Transaction>() - veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) + veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) - verify(tx, never()).show(mockResizeVeilSurface) - verify(tx, never()).show(mockBackgroundSurface) - verify(tx, never()).show(mockIconSurface) - verify(tx).apply() + verify(mockTransaction, never()).show(mockResizeVeilSurface) + verify(mockTransaction, never()).show(mockBackgroundSurface) + verify(mockTransaction, never()).show(mockIconSurface) + verify(mockTransaction).apply() } @Test fun showVeil_alreadyVisible_doesNotShowAgain() { val veil = createResizeVeil() - val tx = mock<SurfaceControl.Transaction>() - veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) - veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) + veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) + veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), false /* fadeIn */) - verify(tx, times(1)).show(mockResizeVeilSurface) - verify(tx, times(1)).show(mockBackgroundSurface) - verify(tx, times(1)).show(mockIconSurface) - verify(tx, times(2)).apply() + verify(mockTransaction, times(1)).show(mockResizeVeilSurface) + verify(mockTransaction, times(1)).show(mockBackgroundSurface) + verify(mockTransaction, times(1)).show(mockIconSurface) + verify(mockTransaction, times(2)).apply() } @Test fun showVeil_reparentsVeilToNewParent() { val veil = createResizeVeil(parent = mock()) - val tx = mock<SurfaceControl.Transaction>() val newParent = mock<SurfaceControl>() - veil.showVeil(tx, newParent, Rect(0, 0, 100, 100), false /* fadeIn */) + veil.showVeil(mockTransaction, newParent, Rect(0, 0, 100, 100), false /* fadeIn */) - verify(tx).reparent(mockResizeVeilSurface, newParent) + verify(mockTransaction).reparent(mockResizeVeilSurface, newParent) } @Test diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index f1ee3256dbee..eecc741a3bbb 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -165,6 +165,15 @@ void MouseCursorController::setStylusHoverMode(bool stylusHoverMode) { } } +void MouseCursorController::setSkipScreenshot(bool skip) { + std::scoped_lock lock(mLock); + if (mLocked.skipScreenshot == skip) { + return; + } + mLocked.skipScreenshot = skip; + updatePointerLocked(); +} + void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) { std::scoped_lock lock(mLock); @@ -352,6 +361,7 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) { mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId); + mLocked.pointerSprite->setSkipScreenshot(mLocked.skipScreenshot); if (mLocked.pointerAlpha > 0) { mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index dc7e8ca16c8a..78f6413ff111 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -53,6 +53,9 @@ public: void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources); void setStylusHoverMode(bool stylusHoverMode); + // Set/Unset flag to hide the mouse cursor on the mirrored display + void setSkipScreenshot(bool skip); + void updatePointerIcon(PointerIconStyle iconId); void setCustomPointerIcon(const SpriteIcon& icon); void reloadPointerResources(bool getAdditionalMouseResources); @@ -94,6 +97,7 @@ private: PointerIconStyle requestedPointerType; PointerIconStyle resolvedPointerType; + bool skipScreenshot{false}; bool animating{false}; } mLocked GUARDED_BY(mLock); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index cca1b07c3118..11b27a214984 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -286,13 +286,16 @@ void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { mCursorController.setCustomPointerIcon(icon); } -void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) { +void PointerController::setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) { std::scoped_lock lock(getLock()); - if (skip) { - mLocked.displaysToSkipScreenshot.insert(displayId); - } else { - mLocked.displaysToSkipScreenshot.erase(displayId); - } + mLocked.displaysToSkipScreenshot.insert(displayId); + mCursorController.setSkipScreenshot(true); +} + +void PointerController::clearSkipScreenshotFlags() { + std::scoped_lock lock(getLock()); + mLocked.displaysToSkipScreenshot.clear(); + mCursorController.setSkipScreenshot(false); } void PointerController::doInactivityTimeout() { diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index c6430f7f36ff..4d1e1d733cc1 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -66,7 +66,8 @@ public: void clearSpots() override; void updatePointerIcon(PointerIconStyle iconId) override; void setCustomPointerIcon(const SpriteIcon& icon) override; - void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override; + void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override; + void clearSkipScreenshotFlags() override; virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 2dcb1f1d1650..cbef68e2eb8f 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -183,12 +183,16 @@ private: MyLooper() : Looper(false) {} ~MyLooper() = default; }; - sp<MyLooper> mLooper; std::thread mThread; + +protected: + sp<MyLooper> mLooper; }; -PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>), - mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) { +PointerControllerTest::PointerControllerTest() + : mPointerSprite(new NiceMock<MockSprite>), + mThread(&PointerControllerTest::loopThread, this), + mLooper(new MyLooper) { mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper)); mPolicy = new MockPointerControllerPolicyInterface(); @@ -339,7 +343,7 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Marking the display to skip screenshot should update sprite as well - mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, true); + mPointerController->setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId::DEFAULT); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true)); // Update spots to sync state with sprite @@ -348,13 +352,53 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Reset flag and verify again - mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false); + mPointerController->clearSkipScreenshotFlags(); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false)); mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, ui::LogicalDisplayId::DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); } +class PointerControllerSkipScreenshotFlagTest + : public PointerControllerTest, + public testing::WithParamInterface<PointerControllerInterface::ControllerType> {}; + +TEST_P(PointerControllerSkipScreenshotFlagTest, updatesSkipScreenshotFlag) { + sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>); + EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite)); + + // Create a pointer controller + mPointerController = + PointerController::create(mPolicy, mLooper, *mSpriteController, GetParam()); + ensureDisplayViewportIsSet(ui::LogicalDisplayId::DEFAULT); + + // By default skip screenshot flag is not set for the sprite + EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(false)); + + // Update pointer to sync state with sprite + mPointerController->setPosition(100, 100); + testing::Mock::VerifyAndClearExpectations(testPointerSprite.get()); + + // Marking the controller to skip screenshot should update pointer sprite + mPointerController->setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId::DEFAULT); + EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(true)); + + // Update pointer to sync state with sprite + mPointerController->move(10, 10); + testing::Mock::VerifyAndClearExpectations(testPointerSprite.get()); + + // Reset flag and verify again + mPointerController->clearSkipScreenshotFlags(); + EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(false)); + mPointerController->move(10, 10); + testing::Mock::VerifyAndClearExpectations(testPointerSprite.get()); +} + +INSTANTIATE_TEST_SUITE_P(PointerControllerSkipScreenshotFlagTest, + PointerControllerSkipScreenshotFlagTest, + testing::Values(PointerControllerInterface::ControllerType::MOUSE, + PointerControllerInterface::ControllerType::STYLUS)); + class PointerControllerWindowInfoListenerTest : public Test {}; TEST_F(PointerControllerWindowInfoListenerTest, 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/AudioSystem.java b/media/java/android/media/AudioSystem.java index 293c561f166c..d148afd3c15d 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1764,6 +1764,10 @@ public class AudioSystem public static native int getForceUse(int usage); /** @hide */ @UnsupportedAppUsage + public static native int setDeviceAbsoluteVolumeEnabled(int nativeDeviceType, + @NonNull String address, boolean enabled, int streamToDriveAbs); + /** @hide */ + @UnsupportedAppUsage public static native int initStreamVolume(int stream, int indexMin, int indexMax); @UnsupportedAppUsage private static native int setStreamVolumeIndex(int stream, int index, int device); 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/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 679e8a1b95e6..5672cd54e369 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -681,8 +681,19 @@ public final class MediaRouter2 { /** * Registers a callback to discover routes and to receive events when they change. * + * <p>Clients can register multiple callbacks, as long as the {@link RouteCallback} instances + * are different. Each callback can provide a unique {@link RouteDiscoveryPreference preference} + * and will only receive updates related to that set preference. + * * <p>If the specified callback is already registered, its registration will be updated for the * given {@link Executor executor} and {@link RouteDiscoveryPreference discovery preference}. + * + * <p>{@link #getInstance(Context) Local routers} must register a route callback to register in + * the system and start receiving updates. Otherwise, all operations will be no-ops. + * + * <p>Any discovery preference passed by a {@link #getInstance(Context, String) proxy router} + * will be ignored and will receive route updates based on the preference set by its matching + * local router. */ public void registerRouteCallback( @NonNull @CallbackExecutor Executor executor, 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..9899e4ec388d 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; @@ -906,6 +901,14 @@ public final class RoutingSessionInfo implements Parcelable { * <p>By default the transfer initiation user handle and package name are set to {@code * null}. */ + // The UserHandleName warning suggests the name should be "doFooAsUser". But the UserHandle + // parameter of this function is stored in a field, and not used to execute an operation on + // a specific user. + // The MissingGetterMatchingBuilder requires a getTransferInitiator function. But said + // getter is not included because the returned package name and user handle is always either + // null or the values that correspond to the calling app, and that information is obtainable + // via RoutingController#wasTransferInitiatedBySelf. + @SuppressWarnings({"UserHandleName", "MissingGetterMatchingBuilder"}) @NonNull @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) public Builder setTransferInitiator( diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroup.java b/media/java/android/media/audiopolicy/AudioVolumeGroup.java index 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/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl index 2fb0af5557b5..7a1cf925bc6d 100644 --- a/media/java/android/media/projection/IMediaProjection.aidl +++ b/media/java/android/media/projection/IMediaProjection.aidl @@ -39,8 +39,8 @@ interface IMediaProjection { void unregisterCallback(IMediaProjectionCallback callback); /** - * Returns the {@link LaunchCookie} identifying the task to record, or {@code null} if - * there is none. + * Returns the {@link LaunchCookie} identifying the task to record. Will always be set + * regardless of starting a new task or recent task */ @EnforcePermission("MANAGE_MEDIA_PROJECTION") @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" @@ -48,8 +48,16 @@ interface IMediaProjection { LaunchCookie getLaunchCookie(); /** - * Updates the {@link LaunchCookie} identifying the task to record, or {@code null} if - * there is none. + * Returns the taskId identifying the task to record. Will only be set in the case of + * launching a recent task, otherwise set to -1. + */ + @EnforcePermission("MANAGE_MEDIA_PROJECTION") + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + int getTaskId(); + + /** + * Updates the {@link LaunchCookie} identifying the task to record. */ @EnforcePermission("MANAGE_MEDIA_PROJECTION") @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" @@ -57,6 +65,15 @@ interface IMediaProjection { void setLaunchCookie(in LaunchCookie launchCookie); /** + * Updates the taskId identifying the task to record. + */ + @EnforcePermission("MANAGE_MEDIA_PROJECTION") + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + void setTaskId(in int taskId); + + + /** * Returns {@code true} if this token is still valid. A token is valid as long as the token * hasn't timed out before it was used, and the token is only used once. * diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 70462effaa54..442ccdcddb2b 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -141,10 +141,9 @@ public final class MediaController { } try { return mSessionBinder.sendMediaButton(mContext.getPackageName(), keyEvent); - } catch (RemoteException e) { - // System is dead. =( + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return false; } /** @@ -155,9 +154,8 @@ public final class MediaController { public @Nullable PlaybackState getPlaybackState() { try { return mSessionBinder.getPlaybackState(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getPlaybackState.", e); - return null; + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -169,9 +167,8 @@ public final class MediaController { public @Nullable MediaMetadata getMetadata() { try { return mSessionBinder.getMetadata(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getMetadata.", e); - return null; + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -185,10 +182,9 @@ public final class MediaController { try { ParceledListSlice list = mSessionBinder.getQueue(); return list == null ? null : list.getList(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getQueue.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -197,10 +193,9 @@ public final class MediaController { public @Nullable CharSequence getQueueTitle() { try { return mSessionBinder.getQueueTitle(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getQueueTitle", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -209,10 +204,9 @@ public final class MediaController { public @Nullable Bundle getExtras() { try { return mSessionBinder.getExtras(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getExtras", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -232,9 +226,8 @@ public final class MediaController { public int getRatingType() { try { return mSessionBinder.getRatingType(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getRatingType.", e); - return Rating.RATING_NONE; + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -246,10 +239,9 @@ public final class MediaController { public long getFlags() { try { return mSessionBinder.getFlags(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getFlags.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return 0; } /** Returns the current playback info for this session. */ @@ -271,10 +263,9 @@ public final class MediaController { public @Nullable PendingIntent getSessionActivity() { try { return mSessionBinder.getLaunchPendingIntent(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getPendingIntent.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -304,8 +295,8 @@ public final class MediaController { // AppOpsManager usages. mSessionBinder.setVolumeTo(mContext.getPackageName(), mContext.getOpPackageName(), value, flags); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling setVolumeTo.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -329,8 +320,8 @@ public final class MediaController { // AppOpsManager usages. mSessionBinder.adjustVolume(mContext.getPackageName(), mContext.getOpPackageName(), direction, flags); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling adjustVolumeBy.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -395,8 +386,8 @@ public final class MediaController { } try { mSessionBinder.sendCommand(mContext.getPackageName(), command, args, cb); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in sendCommand.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -409,8 +400,8 @@ public final class MediaController { if (mPackageName == null) { try { mPackageName = mSessionBinder.getPackageName(); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in getPackageName.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } return mPackageName; @@ -430,8 +421,8 @@ public final class MediaController { // Get info from the connected session. try { mSessionInfo = mSessionBinder.getSessionInfo(); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in getSessionInfo.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } if (mSessionInfo == null) { @@ -454,8 +445,8 @@ public final class MediaController { if (mTag == null) { try { mTag = mSessionBinder.getTag(); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in getTag.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } return mTag; @@ -485,8 +476,8 @@ public final class MediaController { try { mSessionBinder.registerCallback(mContext.getPackageName(), mCbStub); mCbRegistered = true; - } catch (RemoteException e) { - Log.e(TAG, "Dead object in registerCallback", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } } @@ -504,8 +495,8 @@ public final class MediaController { if (mCbRegistered && mCallbacks.size() == 0) { try { mSessionBinder.unregisterCallback(mCbStub); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in removeCallbackLocked"); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } mCbRegistered = false; } @@ -641,8 +632,8 @@ public final class MediaController { public void prepare() { try { mSessionBinder.prepare(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling prepare.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -665,8 +656,8 @@ public final class MediaController { } try { mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mediaId, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling prepare(" + mediaId + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -691,8 +682,8 @@ public final class MediaController { } try { mSessionBinder.prepareFromSearch(mContext.getPackageName(), query, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling prepare(" + query + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -715,8 +706,8 @@ public final class MediaController { } try { mSessionBinder.prepareFromUri(mContext.getPackageName(), uri, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling prepare(" + uri + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -726,8 +717,8 @@ public final class MediaController { public void play() { try { mSessionBinder.play(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -745,8 +736,8 @@ public final class MediaController { } try { mSessionBinder.playFromMediaId(mContext.getPackageName(), mediaId, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play(" + mediaId + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -767,8 +758,8 @@ public final class MediaController { } try { mSessionBinder.playFromSearch(mContext.getPackageName(), query, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play(" + query + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -786,8 +777,8 @@ public final class MediaController { } try { mSessionBinder.playFromUri(mContext.getPackageName(), uri, extras); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play(" + uri + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -798,8 +789,8 @@ public final class MediaController { public void skipToQueueItem(long id) { try { mSessionBinder.skipToQueueItem(mContext.getPackageName(), id); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -810,8 +801,8 @@ public final class MediaController { public void pause() { try { mSessionBinder.pause(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling pause.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -822,8 +813,8 @@ public final class MediaController { public void stop() { try { mSessionBinder.stop(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling stop.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -835,8 +826,8 @@ public final class MediaController { public void seekTo(long pos) { try { mSessionBinder.seekTo(mContext.getPackageName(), pos); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling seekTo.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -847,8 +838,8 @@ public final class MediaController { public void fastForward() { try { mSessionBinder.fastForward(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling fastForward.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -858,8 +849,8 @@ public final class MediaController { public void skipToNext() { try { mSessionBinder.next(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling next.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -870,8 +861,8 @@ public final class MediaController { public void rewind() { try { mSessionBinder.rewind(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling rewind.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -881,8 +872,8 @@ public final class MediaController { public void skipToPrevious() { try { mSessionBinder.previous(mContext.getPackageName()); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling previous.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -896,8 +887,8 @@ public final class MediaController { public void setRating(Rating rating) { try { mSessionBinder.rate(mContext.getPackageName(), rating); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling rate.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -914,8 +905,8 @@ public final class MediaController { } try { mSessionBinder.setPlaybackSpeed(mContext.getPackageName(), speed); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling setPlaybackSpeed.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } @@ -949,8 +940,8 @@ public final class MediaController { } try { mSessionBinder.sendCustomAction(mContext.getPackageName(), action, args); - } catch (RemoteException e) { - Log.d(TAG, "Dead object in sendCustomAction.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } } diff --git a/media/java/android/media/tv/TvStreamConfig.java b/media/java/android/media/tv/TvStreamConfig.java index 7ea93b4f2b23..1f51c7a5dfdf 100644 --- a/media/java/android/media/tv/TvStreamConfig.java +++ b/media/java/android/media/tv/TvStreamConfig.java @@ -23,6 +23,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.util.Objects; + /** * @hide */ @@ -177,4 +179,9 @@ public class TvStreamConfig implements Parcelable { && config.mMaxWidth == mMaxWidth && config.mMaxHeight == mMaxHeight; } + + @Override + public int hashCode() { + return Objects.hash(mGeneration, mStreamId, mType, mMaxWidth, mMaxHeight); + } } diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp index 61b18c88e734..d21cb9319885 100644 --- a/media/tests/MediaRouter/Android.bp +++ b/media/tests/MediaRouter/Android.bp @@ -9,6 +9,7 @@ package { android_test { name: "mediaroutertest", + team: "trendy_team_android_media_solutions", srcs: ["**/*.java"], diff --git a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java index 0df36afddf25..6860c0bb2740 100644 --- a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java +++ b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java @@ -28,6 +28,7 @@ import android.os.RemoteException; * outside the test it is implemented by the system server. */ public final class FakeIMediaProjection extends IMediaProjection.Stub { + int mTaskId = -1; boolean mIsStarted = false; LaunchCookie mLaunchCookie = null; IMediaProjectionCallback mIMediaProjectionCallback = null; @@ -87,6 +88,13 @@ public final class FakeIMediaProjection extends IMediaProjection.Stub { @Override @EnforcePermission(MANAGE_MEDIA_PROJECTION) + public int getTaskId() throws RemoteException { + getTaskId_enforcePermission(); + return mTaskId; + } + + @Override + @EnforcePermission(MANAGE_MEDIA_PROJECTION) public void setLaunchCookie(LaunchCookie launchCookie) throws RemoteException { setLaunchCookie_enforcePermission(); mLaunchCookie = launchCookie; @@ -94,6 +102,13 @@ public final class FakeIMediaProjection extends IMediaProjection.Stub { @Override @EnforcePermission(MANAGE_MEDIA_PROJECTION) + public void setTaskId(int taskId) throws RemoteException { + setTaskId_enforcePermission(); + mTaskId = taskId; + } + + @Override + @EnforcePermission(MANAGE_MEDIA_PROJECTION) public boolean isValid() throws RemoteException { isValid_enforcePermission(); return true; diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml index 39abbb986083..9ddc7ab7ce3d 100644 --- a/packages/CredentialManager/res/values-hr/strings.xml +++ b/packages/CredentialManager/res/values-hr/strings.xml @@ -75,7 +75,7 @@ <string name="get_dialog_title_unlock_options_for" msgid="7096423827682163270">"Otključajte opcije za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"Odaberite spremljeni pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"Odaberite spremljenu zaporku za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> - <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Odaberite spremljene podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"Odaberite podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g> koje želite spremiti"</string> <string name="get_dialog_title_choose_sign_in_for" msgid="645728947702442421">"Odaberite račun za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"Želite li odabrati opciju za <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> <string name="get_dialog_title_use_info_on" msgid="8863708099535435146">"Želite li koristiti te podatke u aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> diff --git a/packages/CredentialManager/wear/robotests/Android.bp b/packages/CredentialManager/wear/robotests/Android.bp index c0a1822a771f..589a3d6cc103 100644 --- a/packages/CredentialManager/wear/robotests/Android.bp +++ b/packages/CredentialManager/wear/robotests/Android.bp @@ -25,4 +25,5 @@ android_robolectric_test { ], java_resource_dirs: ["config"], upstream: true, + strict_mode: false, } 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-af/strings.xml b/packages/InputDevices/res/values-af/strings.xml index 93691400737a..7e2561f1e7db 100644 --- a/packages/InputDevices/res/values-af/strings.xml +++ b/packages/InputDevices/res/values-af/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgies"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thais (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml index 16f64379be41..3053c4485e67 100644 --- a/packages/InputDevices/res/values-am/strings.xml +++ b/packages/InputDevices/res/values-am/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ጂዮርጂያኛ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ታይላንድኛ (ኬድማኒ)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ታይላንድኛ (ፓታሾት)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml index 93223bac47e8..fe8f59c45d30 100644 --- a/packages/InputDevices/res/values-ar/strings.xml +++ b/packages/InputDevices/res/values-ar/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"الجورجية"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"التايلاندية (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"التايلاندية (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"الصربية (اللاتينية)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"لغة الجبل الأسود (اللاتينية)"</string> </resources> diff --git a/packages/InputDevices/res/values-as/strings.xml b/packages/InputDevices/res/values-as/strings.xml index c57b59190f6c..15aa34d31587 100644 --- a/packages/InputDevices/res/values-as/strings.xml +++ b/packages/InputDevices/res/values-as/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"থাই (কেডমানি)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"থাই (পাটাচ’টে)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-az/strings.xml b/packages/InputDevices/res/values-az/strings.xml index 9c6bdb3925b9..765d55bd985c 100644 --- a/packages/InputDevices/res/values-az/strings.xml +++ b/packages/InputDevices/res/values-az/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gürcü"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tay (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tay (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-b+sr+Latn/strings.xml b/packages/InputDevices/res/values-b+sr+Latn/strings.xml index 80ecff5c7c6a..9b52c346e162 100644 --- a/packages/InputDevices/res/values-b+sr+Latn/strings.xml +++ b/packages/InputDevices/res/values-b+sr+Latn/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzijska"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajski (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajski (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-be/strings.xml b/packages/InputDevices/res/values-be/strings.xml index c5aa66f4e273..697ab632aef9 100644 --- a/packages/InputDevices/res/values-be/strings.xml +++ b/packages/InputDevices/res/values-be/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузінская"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайская (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайская (Патачотэ)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Сербская (лацініца)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Чарнагорская (лацініца)"</string> </resources> diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml index 1260d6af08c2..4d70bf555589 100644 --- a/packages/InputDevices/res/values-bg/strings.xml +++ b/packages/InputDevices/res/values-bg/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузински"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"тайландски (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"тайландски (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-bn/strings.xml b/packages/InputDevices/res/values-bn/strings.xml index a038da9eb4d0..7c430d3ddef8 100644 --- a/packages/InputDevices/res/values-bn/strings.xml +++ b/packages/InputDevices/res/values-bn/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"জর্জিয়ান"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"থাই (কেডমানি)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"থাই (পাট্টাচোটে)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-bs/strings.xml b/packages/InputDevices/res/values-bs/strings.xml index 12e93bcbdd92..c47dad3c0eea 100644 --- a/packages/InputDevices/res/values-bs/strings.xml +++ b/packages/InputDevices/res/values-bs/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzijski"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajlandski (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajlandski (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ca/strings.xml b/packages/InputDevices/res/values-ca/strings.xml index 8a1e059c0fd9..fe5a09263539 100644 --- a/packages/InputDevices/res/values-ca/strings.xml +++ b/packages/InputDevices/res/values-ca/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgià"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml index 9ee17e106729..4e3416c4acad 100644 --- a/packages/InputDevices/res/values-cs/strings.xml +++ b/packages/InputDevices/res/values-cs/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzínština"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thajština (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thajština (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-da/strings.xml b/packages/InputDevices/res/values-da/strings.xml index db75d3eac790..c26322453d56 100644 --- a/packages/InputDevices/res/values-da/strings.xml +++ b/packages/InputDevices/res/values-da/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisk"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml index 3db695e72763..587689107811 100644 --- a/packages/InputDevices/res/values-de/strings.xml +++ b/packages/InputDevices/res/values-de/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisch"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thailändisch (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thailändisch (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml index cb7aa2c27bec..fb34edd476eb 100644 --- a/packages/InputDevices/res/values-el/strings.xml +++ b/packages/InputDevices/res/values-el/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Γεωργιανά"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Ταϊλανδικά (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Ταϊλανδικά (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Σερβικά (Λατινικά)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Μαυροβουνιακά (Λατινικά)"</string> </resources> diff --git a/packages/InputDevices/res/values-en-rAU/strings.xml b/packages/InputDevices/res/values-en-rAU/strings.xml index d113201205a7..356ebd48415e 100644 --- a/packages/InputDevices/res/values-en-rAU/strings.xml +++ b/packages/InputDevices/res/values-en-rAU/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml index cae7f00f6745..1d7ba3d3e35d 100644 --- a/packages/InputDevices/res/values-en-rCA/strings.xml +++ b/packages/InputDevices/res/values-en-rCA/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbian (Latin)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Montenegrin (Latin)"</string> </resources> diff --git a/packages/InputDevices/res/values-en-rGB/strings.xml b/packages/InputDevices/res/values-en-rGB/strings.xml index d113201205a7..356ebd48415e 100644 --- a/packages/InputDevices/res/values-en-rGB/strings.xml +++ b/packages/InputDevices/res/values-en-rGB/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-en-rIN/strings.xml b/packages/InputDevices/res/values-en-rIN/strings.xml index d113201205a7..356ebd48415e 100644 --- a/packages/InputDevices/res/values-en-rIN/strings.xml +++ b/packages/InputDevices/res/values-en-rIN/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-en-rXC/strings.xml b/packages/InputDevices/res/values-en-rXC/strings.xml index 71c84da0ae9e..a231d4c28a72 100644 --- a/packages/InputDevices/res/values-en-rXC/strings.xml +++ b/packages/InputDevices/res/values-en-rXC/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbian (Latin)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Montenegrin (Latin)"</string> </resources> diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml index 7490f7d546bd..c20d9280fbc4 100644 --- a/packages/InputDevices/res/values-es-rUS/strings.xml +++ b/packages/InputDevices/res/values-es-rUS/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandés (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandés (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-es/strings.xml b/packages/InputDevices/res/values-es/strings.xml index 22b8cda2d531..39905de11fc0 100644 --- a/packages/InputDevices/res/values-es/strings.xml +++ b/packages/InputDevices/res/values-es/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandés (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandés (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbio (latino)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Montenegrino (latino)"</string> </resources> diff --git a/packages/InputDevices/res/values-et/strings.xml b/packages/InputDevices/res/values-et/strings.xml index 34fd3d7a6978..f2d434070fb7 100644 --- a/packages/InputDevices/res/values-et/strings.xml +++ b/packages/InputDevices/res/values-et/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruusia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tai (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbia (ladina)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Montenegro (ladina)"</string> </resources> diff --git a/packages/InputDevices/res/values-eu/strings.xml b/packages/InputDevices/res/values-eu/strings.xml index 15535fd32da8..57af1f7cfc15 100644 --- a/packages/InputDevices/res/values-eu/strings.xml +++ b/packages/InputDevices/res/values-eu/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiarra"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thailandiarra (kedmanee-a)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thailandiarra (pattachote-a)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml index 11280dd26c9a..6ab841101eb7 100644 --- a/packages/InputDevices/res/values-fa/strings.xml +++ b/packages/InputDevices/res/values-fa/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"گرجستانی"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"تایلندی (کدمانی)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"تایلندی (پاتاچوته)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml index 6c6d4cf1c176..2c69b2929812 100644 --- a/packages/InputDevices/res/values-fi/strings.xml +++ b/packages/InputDevices/res/values-fi/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thai (kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thai (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml index 5c931cf178dd..a4656ffeadca 100644 --- a/packages/InputDevices/res/values-fr-rCA/strings.xml +++ b/packages/InputDevices/res/values-fr-rCA/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Géorgien"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thaï (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thaï (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml index 13236756030e..76c4815b606f 100644 --- a/packages/InputDevices/res/values-fr/strings.xml +++ b/packages/InputDevices/res/values-fr/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Géorgien"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thaï (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thaï (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-gl/strings.xml b/packages/InputDevices/res/values-gl/strings.xml index cedff5b7c545..133fbf746940 100644 --- a/packages/InputDevices/res/values-gl/strings.xml +++ b/packages/InputDevices/res/values-gl/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Xeorxiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandés (kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandés (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-gu/strings.xml b/packages/InputDevices/res/values-gu/strings.xml index cbd4c40c00cd..a3c98ae7a5eb 100644 --- a/packages/InputDevices/res/values-gu/strings.xml +++ b/packages/InputDevices/res/values-gu/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"જ્યોર્જિઅન"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"થાઇ (કેડમાની)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"થાઇ (પટ્ટાશોટે)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml index 7e3df8200b1e..fafc42da686e 100644 --- a/packages/InputDevices/res/values-hi/strings.xml +++ b/packages/InputDevices/res/values-hi/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"जॉर्जियन कीबोर्ड का लेआउट"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"थाई (केडमेनी)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"थाई (पटैचोटे)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml index ba3dc51a4543..d8e7ec49be88 100644 --- a/packages/InputDevices/res/values-hr/strings.xml +++ b/packages/InputDevices/res/values-hr/strings.xml @@ -3,53 +3,57 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="8016145283189546017">"Uređaji za unos"</string> <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android tipkovnica"</string> - <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"engleska (UK)"</string> - <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"engleska (SAD)"</string> - <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engleska (SAD), međunarodna"</string> - <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engleska (SAD), Colemakov raspored"</string> - <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engleska (SAD), Dvorakov raspored"</string> - <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engleska (SAD), raspored Workman"</string> - <string name="keyboard_layout_german_label" msgid="8451565865467909999">"njemačka"</string> - <string name="keyboard_layout_french_label" msgid="813450119589383723">"francuska"</string> + <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"engleski (UK)"</string> + <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"engleski (SAD)"</string> + <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engleski (SAD), međunarodni raspored"</string> + <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engleski (SAD), Colemakov raspored"</string> + <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engleski (SAD), Dvorakov raspored"</string> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"engleski (SAD), raspored Workman"</string> + <string name="keyboard_layout_german_label" msgid="8451565865467909999">"njemački"</string> + <string name="keyboard_layout_french_label" msgid="813450119589383723">"francuski"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francuska (Kanada)"</string> - <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ruska"</string> - <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ruska, raspored Maca"</string> - <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"španjolska"</string> - <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"švicarsko-francuska"</string> - <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švicarsko-njemačka"</string> - <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgijska"</string> - <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bugarska"</string> - <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bugarska (fonetska)"</string> - <string name="keyboard_layout_italian" msgid="6497079660449781213">"talijanska"</string> - <string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string> - <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveška"</string> - <string name="keyboard_layout_swedish" msgid="732959109088479351">"švedska"</string> - <string name="keyboard_layout_finnish" msgid="5585659438924315466">"finska"</string> - <string name="keyboard_layout_croatian" msgid="4172229471079281138">"hrvatska"</string> - <string name="keyboard_layout_czech" msgid="1349256901452975343">"češka"</string> - <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Češka QWERTY tipkovnica"</string> - <string name="keyboard_layout_estonian" msgid="8775830985185665274">"estonska"</string> - <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"mađarska"</string> - <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islandska"</string> - <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"brazilska"</string> - <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugalska"</string> - <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovačka"</string> - <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"slovenska"</string> - <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turska"</string> + <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ruski"</string> + <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ruski, raspored na Macu"</string> + <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"španjolski"</string> + <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"francuski (Švicarska)"</string> + <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"njemački (Švicarska)"</string> + <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgijski raspored"</string> + <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bugarski"</string> + <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bugarski (fonetski)"</string> + <string name="keyboard_layout_italian" msgid="6497079660449781213">"talijanski"</string> + <string name="keyboard_layout_danish" msgid="8036432066627127851">"danski"</string> + <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveški"</string> + <string name="keyboard_layout_swedish" msgid="732959109088479351">"švedski"</string> + <string name="keyboard_layout_finnish" msgid="5585659438924315466">"finski"</string> + <string name="keyboard_layout_croatian" msgid="4172229471079281138">"hrvatski"</string> + <string name="keyboard_layout_czech" msgid="1349256901452975343">"češki"</string> + <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"češki (QWERTY tipkovnica)"</string> + <string name="keyboard_layout_estonian" msgid="8775830985185665274">"estonski"</string> + <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"mađarski"</string> + <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islandski"</string> + <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"portugalski (Brazil)"</string> + <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugalski"</string> + <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovački"</string> + <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"slovenski"</string> + <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turski"</string> <string name="keyboard_layout_turkish_f" msgid="9130320856010776018">"turski F"</string> - <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrajinska"</string> + <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrajinski"</string> <string name="keyboard_layout_arabic" msgid="5671970465174968712">"arapski"</string> <string name="keyboard_layout_greek" msgid="7289253560162386040">"grčki"</string> <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"hebrejski"</string> <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"litavski"</string> <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"španjolski (Latinska Amerika)"</string> - <string name="keyboard_layout_latvian" msgid="4405417142306250595">"latvijska"</string> + <string name="keyboard_layout_latvian" msgid="4405417142306250595">"latvijski"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"perzijski"</string> <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"azerbajdžanski"</string> <string name="keyboard_layout_polish" msgid="1121588624094925325">"poljski"</string> <string name="keyboard_layout_belarusian" msgid="7619281752698687588">"bjeloruski"</string> - <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolski"</string> - <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzijska"</string> - <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajlandski (kedmanee)"</string> - <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tajski (pattachote)"</string> + <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolski"</string> + <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzijski"</string> + <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajski (kedmanee)"</string> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajski (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-hu/strings.xml b/packages/InputDevices/res/values-hu/strings.xml index c42e009d1d71..88c532e5bf88 100644 --- a/packages/InputDevices/res/values-hu/strings.xml +++ b/packages/InputDevices/res/values-hu/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"grúz"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thai (kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thai (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-hy/strings.xml b/packages/InputDevices/res/values-hy/strings.xml index d85cf9dcb2b9..ef4128e94eea 100644 --- a/packages/InputDevices/res/values-hy/strings.xml +++ b/packages/InputDevices/res/values-hy/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"վրացերեն"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"թայերեն (քեդմանի)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"թայերեն (պատաչոտ)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-in/strings.xml b/packages/InputDevices/res/values-in/strings.xml index d504540c6c9c..d4f024ab3fa2 100644 --- a/packages/InputDevices/res/values-in/strings.xml +++ b/packages/InputDevices/res/values-in/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbia (Latin)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Montenegro (Latin)"</string> </resources> diff --git a/packages/InputDevices/res/values-is/strings.xml b/packages/InputDevices/res/values-is/strings.xml index 637874c8b467..680c4e303af7 100644 --- a/packages/InputDevices/res/values-is/strings.xml +++ b/packages/InputDevices/res/values-is/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgíska"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Taílenskt (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Taílenskt (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbneska (latneskt)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Svartfellska (latneskt)"</string> </resources> diff --git a/packages/InputDevices/res/values-it/strings.xml b/packages/InputDevices/res/values-it/strings.xml index eed8316c5af1..97a2359f7de6 100644 --- a/packages/InputDevices/res/values-it/strings.xml +++ b/packages/InputDevices/res/values-it/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml index 8cfe2cbf3e79..0f7a341f8dcd 100644 --- a/packages/InputDevices/res/values-iw/strings.xml +++ b/packages/InputDevices/res/values-iw/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"גיאורגית"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"תאית (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"תאית (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml index d1b334b0f45a..f6cfd433b247 100644 --- a/packages/InputDevices/res/values-ja/strings.xml +++ b/packages/InputDevices/res/values-ja/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ジョージア語"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"タイ語(Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"タイ語(Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ka/strings.xml b/packages/InputDevices/res/values-ka/strings.xml index 8928f684b8b6..4eebe6b30893 100644 --- a/packages/InputDevices/res/values-ka/strings.xml +++ b/packages/InputDevices/res/values-ka/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ქართული"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ტაილანდური (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ტაილანდური (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-kk/strings.xml b/packages/InputDevices/res/values-kk/strings.xml index cf3d3ca78c51..b1ca40aa89b3 100644 --- a/packages/InputDevices/res/values-kk/strings.xml +++ b/packages/InputDevices/res/values-kk/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузин"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тай (кедмани)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тай (паттачот)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-km/strings.xml b/packages/InputDevices/res/values-km/strings.xml index 53eb6f53fbea..b8571ec0a8f2 100644 --- a/packages/InputDevices/res/values-km/strings.xml +++ b/packages/InputDevices/res/values-km/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ហ្សកហ្ស៊ី"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ថៃ (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ថៃ (ប៉ាតាឈោត)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"ស៊ែប៊ី (ឡាតាំង)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"ម៉ុងតេណេហ្គ្រោ (ឡាតាំង)"</string> </resources> diff --git a/packages/InputDevices/res/values-kn/strings.xml b/packages/InputDevices/res/values-kn/strings.xml index c743a6e1c65f..94d65bd664e5 100644 --- a/packages/InputDevices/res/values-kn/strings.xml +++ b/packages/InputDevices/res/values-kn/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ಜಾರ್ಜಿಯನ್"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ಥಾಯ್ (ಕೆಡ್ಮನೀ)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ಥಾಯ್ (ಪಟ್ಟಚೋಟ್)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml index 0e375dd743f3..fa2d9da87f7f 100644 --- a/packages/InputDevices/res/values-ko/strings.xml +++ b/packages/InputDevices/res/values-ko/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"조지아어"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"태국어(Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"태국어(Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ky/strings.xml b/packages/InputDevices/res/values-ky/strings.xml index dad5c9184097..9434840f4ee7 100644 --- a/packages/InputDevices/res/values-ky/strings.xml +++ b/packages/InputDevices/res/values-ky/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузинче"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайча (Kedmanee баскычтобу)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайча (Pattachote баскычтобу)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-lo/strings.xml b/packages/InputDevices/res/values-lo/strings.xml index 0794bde0a9e2..95a8903654f7 100644 --- a/packages/InputDevices/res/values-lo/strings.xml +++ b/packages/InputDevices/res/values-lo/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ຈໍຈຽນ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ໄທ (ເກດມະນີ)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ໄທ (ປັດຕະໂຊຕິ)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-lt/strings.xml b/packages/InputDevices/res/values-lt/strings.xml index 0cceec7cce7c..b9a3e209e578 100644 --- a/packages/InputDevices/res/values-lt/strings.xml +++ b/packages/InputDevices/res/values-lt/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzinų"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tajų („Kedmanee“)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tajų („Pattachote“)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serbų (lotynų rašmenys)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Juodkalniečių (lotynų rašmenys)"</string> </resources> diff --git a/packages/InputDevices/res/values-lv/strings.xml b/packages/InputDevices/res/values-lv/strings.xml index 9b528549b79f..3cd4da79e1c7 100644 --- a/packages/InputDevices/res/values-lv/strings.xml +++ b/packages/InputDevices/res/values-lv/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzīnu"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Taju valoda (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Taju (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-mk/strings.xml b/packages/InputDevices/res/values-mk/strings.xml index 4e8be4637ea5..b91fcc19a18b 100644 --- a/packages/InputDevices/res/values-mk/strings.xml +++ b/packages/InputDevices/res/values-mk/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузиски"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"тајландски (кедмани)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"тајландски (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ml/strings.xml b/packages/InputDevices/res/values-ml/strings.xml index 4b2a5fd40e7b..408ae13b87fc 100644 --- a/packages/InputDevices/res/values-ml/strings.xml +++ b/packages/InputDevices/res/values-ml/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ജോര്ജ്ജിയൻ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"തായ് (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"തായ് (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-mn/strings.xml b/packages/InputDevices/res/values-mn/strings.xml index a7a1799e4345..2490d81923e6 100644 --- a/packages/InputDevices/res/values-mn/strings.xml +++ b/packages/InputDevices/res/values-mn/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Гүрж"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тай (кедмани)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тай (паттачоте)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Серби (латин)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Монтенегро (латин)"</string> </resources> diff --git a/packages/InputDevices/res/values-mr/strings.xml b/packages/InputDevices/res/values-mr/strings.xml index 5e4baa05e77e..47cebf192408 100644 --- a/packages/InputDevices/res/values-mr/strings.xml +++ b/packages/InputDevices/res/values-mr/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"जॉर्जियन"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"थाई (केडमानी)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"थाई (पट्टाचोटे)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ms/strings.xml b/packages/InputDevices/res/values-ms/strings.xml index 9e4c19098af8..9a1c4f7569f3 100644 --- a/packages/InputDevices/res/values-ms/strings.xml +++ b/packages/InputDevices/res/values-ms/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Bahasa Georgia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-my/strings.xml b/packages/InputDevices/res/values-my/strings.xml index 5dbdc7030abe..051024024049 100644 --- a/packages/InputDevices/res/values-my/strings.xml +++ b/packages/InputDevices/res/values-my/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ဂျော်ဂျီယာ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ထိုင်း (ကတ်မနီး)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ထိုင်း (ပတ်တာချုတ်)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"ဆားဘီးယား (လက်တင်)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"မွန်တီနီဂရင်း (လက်တင်)"</string> </resources> diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml index 1e9af3960c4a..25454481fff2 100644 --- a/packages/InputDevices/res/values-nb/strings.xml +++ b/packages/InputDevices/res/values-nb/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisk"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ne/strings.xml b/packages/InputDevices/res/values-ne/strings.xml index ab22576177c7..e85d61571436 100644 --- a/packages/InputDevices/res/values-ne/strings.xml +++ b/packages/InputDevices/res/values-ne/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"जर्जियाली"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"थाई (केडमानी)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"थाई (पत्ताचोते)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml index d28ee9b19157..1893704ea19f 100644 --- a/packages/InputDevices/res/values-nl/strings.xml +++ b/packages/InputDevices/res/values-nl/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisch"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Servisch (Latijns)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Montenegrijns (Latijns)"</string> </resources> diff --git a/packages/InputDevices/res/values-or/strings.xml b/packages/InputDevices/res/values-or/strings.xml index e92c15566dc4..8df615efedb7 100644 --- a/packages/InputDevices/res/values-or/strings.xml +++ b/packages/InputDevices/res/values-or/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ଜର୍ଜିଆନ୍"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ଥାଇ (କେଡମାନି)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ଥାଇ (ପାଟ୍ଟାଚୋଟେ)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-pa/strings.xml b/packages/InputDevices/res/values-pa/strings.xml index f766297c0d35..b0a140e8b666 100644 --- a/packages/InputDevices/res/values-pa/strings.xml +++ b/packages/InputDevices/res/values-pa/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ਜਾਰਜੀਆਈ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ਥਾਈ (ਕੇਦਮਨੀ)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ਥਾਈ (ਪੈਟਾਸ਼ੋਟੇ)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml index e202463b97de..b76c0fec702f 100644 --- a/packages/InputDevices/res/values-pl/strings.xml +++ b/packages/InputDevices/res/values-pl/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruziński"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajski (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajski (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"serbski (alfabet łaciński)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"czarnogórski (alfabet łaciński)"</string> </resources> diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml index 4a0c3be58def..6fa852bc60ae 100644 --- a/packages/InputDevices/res/values-pt-rBR/strings.xml +++ b/packages/InputDevices/res/values-pt-rBR/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-pt-rPT/strings.xml b/packages/InputDevices/res/values-pt-rPT/strings.xml index c54b620ee6fd..b7684672bced 100644 --- a/packages/InputDevices/res/values-pt-rPT/strings.xml +++ b/packages/InputDevices/res/values-pt-rPT/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml index 4a0c3be58def..6fa852bc60ae 100644 --- a/packages/InputDevices/res/values-pt/strings.xml +++ b/packages/InputDevices/res/values-pt/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml index d91635bff4a8..9dc2841832b1 100644 --- a/packages/InputDevices/res/values-ro/strings.xml +++ b/packages/InputDevices/res/values-ro/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiană"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thailandeză (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thailandeză (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml index da1a83a59cf3..9612717bfe1a 100644 --- a/packages/InputDevices/res/values-ru/strings.xml +++ b/packages/InputDevices/res/values-ru/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузинский"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайский (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайский (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-si/strings.xml b/packages/InputDevices/res/values-si/strings.xml index 97aed6286a8c..2151f4492fc7 100644 --- a/packages/InputDevices/res/values-si/strings.xml +++ b/packages/InputDevices/res/values-si/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ජෝර්ජියානු"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"තායි (කෙඩ්මනී)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"තායි (පට්ටචෝටේ)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-sk/strings.xml b/packages/InputDevices/res/values-sk/strings.xml index 6f387ad2c6bb..c8b602101761 100644 --- a/packages/InputDevices/res/values-sk/strings.xml +++ b/packages/InputDevices/res/values-sk/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzínske"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thajčina (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thajčina (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml index 32ca0ad34c02..1e04ae11f2c3 100644 --- a/packages/InputDevices/res/values-sl/strings.xml +++ b/packages/InputDevices/res/values-sl/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzinščina"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajščina (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajščina (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-sq/strings.xml b/packages/InputDevices/res/values-sq/strings.xml index c33ba4af578d..8ad13f442279 100644 --- a/packages/InputDevices/res/values-sq/strings.xml +++ b/packages/InputDevices/res/values-sq/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gjeorgjisht"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tajlandisht (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tajlandisht (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml index 0b434d7c8cb1..28cd5caf1c99 100644 --- a/packages/InputDevices/res/values-sr/strings.xml +++ b/packages/InputDevices/res/values-sr/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузијска"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"тајски (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"тајски (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml index 3d08415081fe..c24c300f4f6d 100644 --- a/packages/InputDevices/res/values-sv/strings.xml +++ b/packages/InputDevices/res/values-sv/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgiska"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thailändska (pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml index 42714a55530f..0cf002ebdd76 100644 --- a/packages/InputDevices/res/values-sw/strings.xml +++ b/packages/InputDevices/res/values-sw/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Kijojia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Kithai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Kitai (Kipatachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ta/strings.xml b/packages/InputDevices/res/values-ta/strings.xml index f8bc751c3ca5..87e9105b0796 100644 --- a/packages/InputDevices/res/values-ta/strings.xml +++ b/packages/InputDevices/res/values-ta/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ஜார்ஜியன்"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"தாய் (கேட்மேனி)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"தாய் (பட்டாசொட்டே)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-te/strings.xml b/packages/InputDevices/res/values-te/strings.xml index 2c1c1f8021fa..4cf1b1419692 100644 --- a/packages/InputDevices/res/values-te/strings.xml +++ b/packages/InputDevices/res/values-te/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"జార్జియన్"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"థాయ్ (కెడ్మనీ)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"థాయ్ (పత్తచోత్)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-th/strings.xml b/packages/InputDevices/res/values-th/strings.xml index 3b96226bbe81..88cf752ebc16 100644 --- a/packages/InputDevices/res/values-th/strings.xml +++ b/packages/InputDevices/res/values-th/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ภาษาจอร์เจีย"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ไทย (เกษมณี)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ไทย (ปัตตะโชติ)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-tl/strings.xml b/packages/InputDevices/res/values-tl/strings.xml index f0cd0f8ce46d..787c85149034 100644 --- a/packages/InputDevices/res/values-tl/strings.xml +++ b/packages/InputDevices/res/values-tl/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml index a5c89d746b58..62360b528230 100644 --- a/packages/InputDevices/res/values-tr/strings.xml +++ b/packages/InputDevices/res/values-tr/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gürcüce"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tayca (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tayca (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml index dd3aab8c2aac..15b1a2563052 100644 --- a/packages/InputDevices/res/values-uk/strings.xml +++ b/packages/InputDevices/res/values-uk/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузинська"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайська (кедмані)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайська (паттачоте)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-ur/strings.xml b/packages/InputDevices/res/values-ur/strings.xml index 008cd103c2da..d10c79883f1b 100644 --- a/packages/InputDevices/res/values-ur/strings.xml +++ b/packages/InputDevices/res/values-ur/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"جارجیائی"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"تھائی (کیڈمینی)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"تھائی (پٹاچوٹے)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-uz/strings.xml b/packages/InputDevices/res/values-uz/strings.xml index 2c1c4b064dcd..0e80d71ba56a 100644 --- a/packages/InputDevices/res/values-uz/strings.xml +++ b/packages/InputDevices/res/values-uz/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzin"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tay (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tay (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Serb (lotin)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Chernogor (lotin)"</string> </resources> diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml index b5a0b16befb8..5094a29d04c0 100644 --- a/packages/InputDevices/res/values-vi/strings.xml +++ b/packages/InputDevices/res/values-vi/strings.xml @@ -52,4 +52,6 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Tiếng Georgia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tiếng Thái (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tiếng Thái (Pattachote)"</string> + <string name="keyboard_layout_serbian_latin" msgid="3128791759390046571">"Tiếng Serbia (Latinh)"</string> + <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Tiếng Montenegro (Latinh)"</string> </resources> diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml index 97e75e6c5372..5934e3b512bc 100644 --- a/packages/InputDevices/res/values-zh-rCN/strings.xml +++ b/packages/InputDevices/res/values-zh-rCN/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"格鲁吉亚语"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"泰语 (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"泰语 (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-zh-rHK/strings.xml b/packages/InputDevices/res/values-zh-rHK/strings.xml index 45d4b4fd5b40..dbcfd1ce1392 100644 --- a/packages/InputDevices/res/values-zh-rHK/strings.xml +++ b/packages/InputDevices/res/values-zh-rHK/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"格魯吉亞文"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"泰文 (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"泰文 (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-zh-rTW/strings.xml b/packages/InputDevices/res/values-zh-rTW/strings.xml index f0ea94bfba28..c87f2ac398d3 100644 --- a/packages/InputDevices/res/values-zh-rTW/strings.xml +++ b/packages/InputDevices/res/values-zh-rTW/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"喬治亞文"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"泰文 (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"泰文 (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml index 079b841084c4..f62afba2018f 100644 --- a/packages/InputDevices/res/values-zu/strings.xml +++ b/packages/InputDevices/res/values-zu/strings.xml @@ -52,4 +52,8 @@ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Isi-Thai (Kedmanee)"</string> <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Isi-Thai (Pattachote)"</string> + <!-- no translation found for keyboard_layout_serbian_latin (3128791759390046571) --> + <skip /> + <!-- no translation found for keyboard_layout_montenegrin_latin (1467832503378949945) --> + <skip /> </resources> 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/PackageInstaller/res/values-bg/strings.xml b/packages/PackageInstaller/res/values-bg/strings.xml index f6efdf677343..b844054e8679 100644 --- a/packages/PackageInstaller/res/values-bg/strings.xml +++ b/packages/PackageInstaller/res/values-bg/strings.xml @@ -63,7 +63,7 @@ <string name="archive_application_text_all_users" msgid="3151229641681672580">"Да се архивира ли това приложение за всички потребители? Личните ви данни ще бъдат запазени"</string> <string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"Да се архивира ли това приложение в служебния ви потребителски профил? Личните ви данни ще бъдат запазени"</string> <string name="archive_application_text_user" msgid="2586558895535581451">"Да се архивира ли това приложение за <xliff:g id="USERNAME">%1$s</xliff:g>? Личните ви данни ще бъдат запазени"</string> - <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"Искате ли да архивирате това приложение от личното си пространство? Личните ви данни ще бъдат запазени"</string> + <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"Искате ли да архивирате това приложение от частното си пространство? Личните ви данни ще бъдат запазени"</string> <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Искате ли да деинсталирате това приложение за "<b>"всички"</b>" потребители? Приложението и данните му ще бъдат премахнати от "<b>"всички"</b>" потребители на устройството."</string> <string name="uninstall_application_text_user" msgid="498072714173920526">"Искате ли да деинсталирате това приложение за потребителя <xliff:g id="USERNAME">%1$s</xliff:g>?"</string> <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Искате ли да деинсталирате това приложение от служебния си потребителски профил?"</string> @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Запазване на <xliff:g id="SIZE">%1$s</xliff:g> данни от приложението."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Искате ли да изтриете това приложение?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Искате ли да деинсталирате това приложение? Копието на <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> също ще бъде изтрито."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Искате ли да деинсталирате това приложение от личното си пространство?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Искате ли да деинсталирате това приложение от частното си пространство?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Активни деинсталирания"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Неуспешни деинсталирания"</string> <string name="uninstalling" msgid="8709566347688966845">"Деинсталира се..."</string> diff --git a/packages/PrintSpooler/res/values-kk/strings.xml b/packages/PrintSpooler/res/values-kk/strings.xml index 939e1b43d2bc..1755c7a313e4 100644 --- a/packages/PrintSpooler/res/values-kk/strings.xml +++ b/packages/PrintSpooler/res/values-kk/strings.xml @@ -74,7 +74,7 @@ <string name="enabled_services_title" msgid="7036986099096582296">"Қосылған қызметтер"</string> <string name="recommended_services_title" msgid="3799434882937956924">"Ұсынылған қызметтер"</string> <string name="disabled_services_title" msgid="7313253167968363211">"Өшірілген қызметтер"</string> - <string name="all_services_title" msgid="5578662754874906455">"Барлық қызметтер"</string> + <string name="all_services_title" msgid="5578662754874906455">"Барлық қызмет"</string> <plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138"> <item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> принтерді табу үшін орнатыңыз</item> <item quantity="one"><xliff:g id="COUNT_0">%1$s</xliff:g> принтерді табу үшін орнатыңыз</item> diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt index 82423473e682..b4a91726ac1d 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt @@ -138,7 +138,7 @@ class BackupRestoreStorageManager private constructor(private val application: A private fun notifyBackupManager(key: Any?, reason: Int) { val name = storage.name // prefer not triggering backup immediately after restore - if (reason == ChangeReason.RESTORE) { + if (reason == DataChangeReason.RESTORE) { Log.d( LOG_TAG, "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key" @@ -161,8 +161,8 @@ class BackupRestoreStorageManager private constructor(private val application: A fun notifyRestoreFinished() { when (storage) { - is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE) - is Observable -> storage.notifyChange(ChangeReason.RESTORE) + is KeyedObservable<*> -> storage.notifyChange(DataChangeReason.RESTORE) + is Observable -> storage.notifyChange(DataChangeReason.RESTORE) } } } diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt new file mode 100644 index 000000000000..145fabea52af --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.datastore + +import androidx.annotation.IntDef + +/** The reason of data change. */ +@IntDef( + DataChangeReason.UNKNOWN, + DataChangeReason.UPDATE, + DataChangeReason.DELETE, + DataChangeReason.RESTORE, + DataChangeReason.SYNC_ACROSS_PROFILES, +) +@Retention(AnnotationRetention.SOURCE) +annotation class DataChangeReason { + companion object { + /** Unknown reason of the change. */ + const val UNKNOWN = 0 + /** Data is updated. */ + const val UPDATE = 1 + /** Data is deleted. */ + const val DELETE = 2 + /** Data is restored from backup/restore framework. */ + const val RESTORE = 3 + /** Data is synced from another profile (e.g. personal profile to work profile). */ + const val SYNC_ACROSS_PROFILES = 4 + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt index 3ed4d46459cf..ede7c63d00b4 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt @@ -37,7 +37,7 @@ fun interface KeyedObserver<in K> { * @param reason the reason of change * @see KeyedObservable.addObserver */ - fun onKeyChanged(key: K, @ChangeReason reason: Int) + fun onKeyChanged(key: K, reason: Int) } /** @@ -89,7 +89,7 @@ interface KeyedObservable<K> { * * @param reason reason of the change */ - fun notifyChange(@ChangeReason reason: Int) + fun notifyChange(reason: Int) /** * Notifies observers that a change occurs on given key. @@ -99,7 +99,7 @@ interface KeyedObservable<K> { * @param key key of the change * @param reason reason of the change */ - fun notifyChange(key: K, @ChangeReason reason: Int) + fun notifyChange(key: K, reason: Int) } /** A thread safe implementation of [KeyedObservable]. */ @@ -141,7 +141,7 @@ class KeyedDataObservable<K> : KeyedObservable<K> { } } - override fun notifyChange(@ChangeReason reason: Int) { + override fun notifyChange(reason: Int) { // make a copy to avoid potential ConcurrentModificationException val observers = synchronized(observers) { observers.entries.toTypedArray() } val keyedObservers = synchronized(keyedObservers) { keyedObservers.copy() } @@ -165,7 +165,7 @@ class KeyedDataObservable<K> : KeyedObservable<K> { return result } - override fun notifyChange(key: K, @ChangeReason reason: Int) { + override fun notifyChange(key: K, reason: Int) { // make a copy to avoid potential ConcurrentModificationException val observers = synchronized(observers) { observers.entries.toTypedArray() } val keyedObservers = diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt index 6d0ca6690c9f..98d0f6e3f9a1 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt @@ -18,34 +18,9 @@ package com.android.settingslib.datastore import androidx.annotation.AnyThread import androidx.annotation.GuardedBy -import androidx.annotation.IntDef import java.util.WeakHashMap import java.util.concurrent.Executor -/** The reason of a change. */ -@IntDef( - ChangeReason.UNKNOWN, - ChangeReason.UPDATE, - ChangeReason.DELETE, - ChangeReason.RESTORE, - ChangeReason.SYNC_ACROSS_PROFILES, -) -@Retention(AnnotationRetention.SOURCE) -annotation class ChangeReason { - companion object { - /** Unknown reason of the change. */ - const val UNKNOWN = 0 - /** Data is updated. */ - const val UPDATE = 1 - /** Data is deleted. */ - const val DELETE = 2 - /** Data is restored from backup/restore framework. */ - const val RESTORE = 3 - /** Data is synced from another profile (e.g. personal profile to work profile). */ - const val SYNC_ACROSS_PROFILES = 4 - } -} - /** * Callback to be informed of changes in [Observable] object. * @@ -60,7 +35,7 @@ fun interface Observer { * @param reason the reason of change * @see [Observable.addObserver] for the notices. */ - fun onChanged(@ChangeReason reason: Int) + fun onChanged(reason: Int) } /** An observable object allows to observe change with [Observer]. */ @@ -90,7 +65,7 @@ interface Observable { * * @param reason reason of the change */ - fun notifyChange(@ChangeReason reason: Int) + fun notifyChange(reason: Int) } /** A thread safe implementation of [Observable]. */ @@ -110,7 +85,7 @@ class DataObservable : Observable { synchronized(observers) { observers.remove(observer) } } - override fun notifyChange(@ChangeReason reason: Int) { + override fun notifyChange(reason: Int) { // make a copy to avoid potential ConcurrentModificationException val entries = synchronized(observers) { observers.entries.toTypedArray() } for (entry in entries) { diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt index 9f9c0d839744..20a95d7efc4b 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt @@ -83,10 +83,10 @@ constructor( private val sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> if (key != null) { - notifyChange(key, ChangeReason.UPDATE) + notifyChange(key, DataChangeReason.UPDATE) } else { // On Android >= R, SharedPreferences.Editor.clear() will trigger this case - notifyChange(ChangeReason.DELETE) + notifyChange(DataChangeReason.DELETE) } } diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp index 5d000ebe9417..2e3b42de5b9d 100644 --- a/packages/SettingsLib/DataStore/tests/Android.bp +++ b/packages/SettingsLib/DataStore/tests/Android.bp @@ -26,4 +26,5 @@ android_robolectric_test { instrumentation_for: "SettingsLibDataStoreShell", coverage_libs: ["SettingsLibDataStore"], upstream: true, + strict_mode: false, } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt index d8f502854402..19c574a843ca 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt @@ -157,9 +157,9 @@ class BackupRestoreStorageManagerTest { manager.onRestoreFinished() - verify(keyedObserver).onKeyChanged("key", ChangeReason.RESTORE) - verify(anyKeyObserver).onKeyChanged(null, ChangeReason.RESTORE) - verify(observer).onChanged(ChangeReason.RESTORE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.RESTORE) + verify(anyKeyObserver).onKeyChanged(null, DataChangeReason.RESTORE) + verify(observer).onChanged(DataChangeReason.RESTORE) if (isRobolectric()) { Shadows.shadowOf(BackupManager(application)).apply { assertThat(isDataChanged).isFalse() @@ -186,8 +186,8 @@ class BackupRestoreStorageManagerTest { assertThat(dataChangedCount).isEqualTo(0) } - fileStorage.notifyChange(ChangeReason.UPDATE) - verify(observer).onChanged(ChangeReason.UPDATE) + fileStorage.notifyChange(DataChangeReason.UPDATE) + verify(observer).onChanged(DataChangeReason.UPDATE) verify(keyedObserver, never()).onKeyChanged(any(), any()) verify(anyKeyObserver, never()).onKeyChanged(any(), any()) reset(observer) @@ -196,10 +196,10 @@ class BackupRestoreStorageManagerTest { assertThat(dataChangedCount).isEqualTo(1) } - keyedStorage.notifyChange("key", ChangeReason.DELETE) + keyedStorage.notifyChange("key", DataChangeReason.DELETE) verify(observer, never()).onChanged(any()) - verify(keyedObserver).onKeyChanged("key", ChangeReason.DELETE) - verify(anyKeyObserver).onKeyChanged("key", ChangeReason.DELETE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.DELETE) + verify(anyKeyObserver).onKeyChanged("key", DataChangeReason.DELETE) backupManager?.apply { assertThat(isDataChanged).isTrue() assertThat(dataChangedCount).isEqualTo(2) @@ -207,11 +207,11 @@ class BackupRestoreStorageManagerTest { reset(keyedObserver) // backup manager is not notified for restore event - fileStorage.notifyChange(ChangeReason.RESTORE) - keyedStorage.notifyChange("key", ChangeReason.RESTORE) - verify(observer).onChanged(ChangeReason.RESTORE) - verify(keyedObserver).onKeyChanged("key", ChangeReason.RESTORE) - verify(anyKeyObserver).onKeyChanged("key", ChangeReason.RESTORE) + fileStorage.notifyChange(DataChangeReason.RESTORE) + keyedStorage.notifyChange("key", DataChangeReason.RESTORE) + verify(observer).onChanged(DataChangeReason.RESTORE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.RESTORE) + verify(anyKeyObserver).onKeyChanged("key", DataChangeReason.RESTORE) backupManager?.apply { assertThat(isDataChanged).isTrue() assertThat(dataChangedCount).isEqualTo(2) diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt index 8638b2f20b52..0fdecb034f83 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt @@ -77,7 +77,7 @@ class KeyedObserverTest { var observer: KeyedObserver<Any?>? = KeyedObserver { _, _ -> counter.incrementAndGet() } keyedObservable.addObserver(observer!!, executor1) - keyedObservable.notifyChange(ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) // trigger GC, the observer callback should not be invoked @@ -85,7 +85,7 @@ class KeyedObserverTest { System.gc() System.runFinalization() - keyedObservable.notifyChange(ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) } @@ -95,7 +95,7 @@ class KeyedObserverTest { var keyObserver: KeyedObserver<Any>? = KeyedObserver { _, _ -> counter.incrementAndGet() } keyedObservable.addObserver(key1, keyObserver!!, executor1) - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) // trigger GC, the observer callback should not be invoked @@ -103,7 +103,7 @@ class KeyedObserverTest { System.gc() System.runFinalization() - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) } @@ -112,16 +112,16 @@ class KeyedObserverTest { keyedObservable.addObserver(observer1, executor1) keyedObservable.addObserver(observer2, executor2) - keyedObservable.notifyChange(ChangeReason.UPDATE) - verify(observer1).onKeyChanged(null, ChangeReason.UPDATE) - verify(observer2).onKeyChanged(null, ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) + verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE) + verify(observer2).onKeyChanged(null, DataChangeReason.UPDATE) reset(observer1, observer2) keyedObservable.removeObserver(observer2) - keyedObservable.notifyChange(ChangeReason.DELETE) - verify(observer1).onKeyChanged(null, ChangeReason.DELETE) - verify(observer2, never()).onKeyChanged(null, ChangeReason.DELETE) + keyedObservable.notifyChange(DataChangeReason.DELETE) + verify(observer1).onKeyChanged(null, DataChangeReason.DELETE) + verify(observer2, never()).onKeyChanged(null, DataChangeReason.DELETE) } @Test @@ -129,16 +129,16 @@ class KeyedObserverTest { keyedObservable.addObserver(key1, keyedObserver1, executor1) keyedObservable.addObserver(key2, keyedObserver2, executor2) - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) - verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver2, never()).onKeyChanged(key2, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) + verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE) + verify(keyedObserver2, never()).onKeyChanged(key2, DataChangeReason.UPDATE) reset(keyedObserver1, keyedObserver2) keyedObservable.removeObserver(key1, keyedObserver1) - keyedObservable.notifyChange(key1, ChangeReason.DELETE) - verify(keyedObserver1, never()).onKeyChanged(key1, ChangeReason.DELETE) - verify(keyedObserver2, never()).onKeyChanged(key2, ChangeReason.DELETE) + keyedObservable.notifyChange(key1, DataChangeReason.DELETE) + verify(keyedObserver1, never()).onKeyChanged(key1, DataChangeReason.DELETE) + verify(keyedObserver2, never()).onKeyChanged(key2, DataChangeReason.DELETE) } @Test @@ -147,24 +147,24 @@ class KeyedObserverTest { keyedObservable.addObserver(key1, keyedObserver1, executor1) keyedObservable.addObserver(key2, keyedObserver2, executor1) - keyedObservable.notifyChange(ChangeReason.UPDATE) - verify(observer1).onKeyChanged(null, ChangeReason.UPDATE) - verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver2).onKeyChanged(key2, ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) + verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE) + verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE) + verify(keyedObserver2).onKeyChanged(key2, DataChangeReason.UPDATE) reset(observer1, keyedObserver1, keyedObserver2) - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) - verify(observer1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver2, never()).onKeyChanged(key1, ChangeReason.UPDATE) + verify(observer1).onKeyChanged(key1, DataChangeReason.UPDATE) + verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE) + verify(keyedObserver2, never()).onKeyChanged(key1, DataChangeReason.UPDATE) reset(observer1, keyedObserver1, keyedObserver2) - keyedObservable.notifyChange(key2, ChangeReason.UPDATE) + keyedObservable.notifyChange(key2, DataChangeReason.UPDATE) - verify(observer1).onKeyChanged(key2, ChangeReason.UPDATE) - verify(keyedObserver1, never()).onKeyChanged(key2, ChangeReason.UPDATE) - verify(keyedObserver2).onKeyChanged(key2, ChangeReason.UPDATE) + verify(observer1).onKeyChanged(key2, DataChangeReason.UPDATE) + verify(keyedObserver1, never()).onKeyChanged(key2, DataChangeReason.UPDATE) + verify(keyedObserver2).onKeyChanged(key2, DataChangeReason.UPDATE) } @Test @@ -176,7 +176,7 @@ class KeyedObserverTest { keyedObservable.addObserver(observer, executor1) - keyedObservable.notifyChange(ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) keyedObservable.removeObserver(observer) } @@ -189,7 +189,7 @@ class KeyedObserverTest { keyedObservable.addObserver(key1, keyObserver, executor1) - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) keyedObservable.removeObserver(key1, keyObserver) } } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt index 173c2b1d4b81..5d0303c06d41 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt @@ -58,7 +58,7 @@ class ObserverTest { var observer: Observer? = Observer { counter.incrementAndGet() } observable.addObserver(observer!!, executor1) - observable.notifyChange(ChangeReason.UPDATE) + observable.notifyChange(DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) // trigger GC, the observer callback should not be invoked @@ -66,7 +66,7 @@ class ObserverTest { System.gc() System.runFinalization() - observable.notifyChange(ChangeReason.UPDATE) + observable.notifyChange(DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) } @@ -75,17 +75,17 @@ class ObserverTest { observable.addObserver(observer1, executor1) observable.addObserver(observer2, executor2) - observable.notifyChange(ChangeReason.DELETE) + observable.notifyChange(DataChangeReason.DELETE) - verify(observer1).onChanged(ChangeReason.DELETE) - verify(observer2).onChanged(ChangeReason.DELETE) + verify(observer1).onChanged(DataChangeReason.DELETE) + verify(observer2).onChanged(DataChangeReason.DELETE) reset(observer1, observer2) observable.removeObserver(observer2) - observable.notifyChange(ChangeReason.UPDATE) - verify(observer1).onChanged(ChangeReason.UPDATE) - verify(observer2, never()).onChanged(ChangeReason.UPDATE) + observable.notifyChange(DataChangeReason.UPDATE) + verify(observer1).onChanged(DataChangeReason.UPDATE) + verify(observer2, never()).onChanged(DataChangeReason.UPDATE) } @Test @@ -93,7 +93,7 @@ class ObserverTest { // ConcurrentModificationException is raised if it is not implemented correctly val observer = Observer { observable.addObserver(observer1, executor1) } observable.addObserver(observer, executor1) - observable.notifyChange(ChangeReason.UPDATE) + observable.notifyChange(DataChangeReason.UPDATE) observable.removeObserver(observer) } } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt index fec7d758b893..a135d77e6d25 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt @@ -80,13 +80,13 @@ class SharedPreferencesStorageTest { storage.addObserver("key", keyedObserver, executor) storage.sharedPreferences.edit().putString("key", "string").applySync() - verify(observer).onKeyChanged("key", ChangeReason.UPDATE) - verify(keyedObserver).onKeyChanged("key", ChangeReason.UPDATE) + verify(observer).onKeyChanged("key", DataChangeReason.UPDATE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.UPDATE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { storage.sharedPreferences.edit().clear().applySync() - verify(observer).onKeyChanged(null, ChangeReason.DELETE) - verify(keyedObserver).onKeyChanged("key", ChangeReason.DELETE) + verify(observer).onKeyChanged(null, DataChangeReason.DELETE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.DELETE) } } diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp index c3a91a20c339..cd8f584953aa 100644 --- a/packages/SettingsLib/IllustrationPreference/Android.bp +++ b/packages/SettingsLib/IllustrationPreference/Android.bp @@ -47,6 +47,7 @@ java_aconfig_library { aconfig_declarations: "settingslib_illustrationpreference_flags", min_sdk_version: "30", + sdk_version: "system_current", apex_available: [ "//apex_available:platform", diff --git a/packages/SettingsLib/ProfileSelector/res/values-cs/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-cs/strings.xml index d50fc9a7da88..3e5f52696916 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-cs/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-cs/strings.xml @@ -18,6 +18,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Osobní"</string> - <string name="settingslib_category_work" msgid="4867750733682444676">"Prácovní"</string> + <string name="settingslib_category_work" msgid="4867750733682444676">"Pracovní"</string> <string name="settingslib_category_private" msgid="5039276873477591386">"Soukromé"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-el/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-el/strings.xml index 628388d696b8..b1bc69ce7a41 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-el/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-el/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Προσωπικά"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"Εργασία"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Ιδιωτικό"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Ιδιωτικός"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-is/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-is/strings.xml index 75668e8c55ff..b7aa61bf8566 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-is/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-is/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Persónulegt"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"Vinna"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Lokað"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Laynirými"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-ja/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ja/strings.xml index 21419e65e408..c9faa3c353c5 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-ja/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-ja/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"個人用"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"仕事用"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"非公開"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"プライベート"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-mk/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-mk/strings.xml index 07cf9c71e5f3..e8c2bf563c40 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-mk/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-mk/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Лични"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"Работа"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Приватен"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Приватно"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml index e1e68c758a15..1e4abc1e2025 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml @@ -18,6 +18,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"ਨਿੱਜੀ"</string> - <string name="settingslib_category_work" msgid="4867750733682444676">"ਕਾਰਜ"</string> + <string name="settingslib_category_work" msgid="4867750733682444676">"ਕੰਮ ਸੰਬੰਧੀ"</string> <string name="settingslib_category_private" msgid="5039276873477591386">"ਪ੍ਰਾਈਵੇਟ"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-ru/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ru/strings.xml index ee4212f82ba4..e0b1471c0859 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-ru/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-ru/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Личный профиль"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"Рабочий профиль"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Личное"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Частный профиль"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-tr/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-tr/strings.xml index 59f21c8c8ba6..e4e2bdcc44f0 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-tr/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-tr/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Kişisel"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"İş"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Gizli"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Özel"</string> </resources> diff --git a/packages/SettingsLib/Spa/screenshot/robotests/Android.bp b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp index 6b8197c03e75..c834c80ad536 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/Android.bp +++ b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp @@ -71,4 +71,6 @@ android_robolectric_test { upstream: true, java_resource_dirs: ["config"], instrumentation_for: "SpaRoboApp", + + strict_mode: false, } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt index e91fa65401a4..e9f9689cb319 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt @@ -18,9 +18,9 @@ package com.android.settingslib.spa.framework.compose import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner @Composable fun LifecycleEffect( diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt index 3991f26e1b0c..0b1c92d78a57 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt @@ -24,7 +24,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner /** * An effect for detecting presses of the system back button, and the back event will not be diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt index ee24a09d4395..007f47bd3c82 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt @@ -21,8 +21,8 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.slice.widget.SliceLiveData import androidx.slice.widget.SliceView diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt index de080e3d8ef4..022ddedd1062 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.window.DialogProperties data class AlertDialogButton( val text: String, + val enabled: Boolean = true, val onClick: () -> Unit = {}, ) @@ -114,6 +115,7 @@ private fun AlertDialogPresenter.Button(button: AlertDialogButton) { close() button.onClick() }, + enabled = button.enabled, ) { Text(button.text) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt index b471e50be275..bdbe62c07425 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -65,7 +66,7 @@ internal fun DropdownTextBox( OutlinedTextField( // The `menuAnchor` modifier must be passed to the text field for correctness. modifier = Modifier - .menuAnchor() + .menuAnchor(MenuAnchorType.PrimaryNotEditable) .fillMaxWidth(), value = text, onValueChange = { }, diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt index fe7baff43101..8b0efff591f2 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt @@ -18,9 +18,9 @@ package com.android.settingslib.spa.framework.compose import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.test.junit4.createComposeRule import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.testing.TestLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt index 9468f95a094e..20ea397d7222 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt @@ -20,6 +20,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -67,7 +69,18 @@ class SettingsAlertDialogTest { rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT)) } - composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed() + composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed().assertIsEnabled() + } + + @Test + fun confirmButton_disabled() { + setAndOpenDialog { + rememberAlertDialogPresenter( + confirmButton = AlertDialogButton(text = CONFIRM_TEXT, enabled = false) + ) + } + + composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed().assertIsNotEnabled() } @Test @@ -90,7 +103,18 @@ class SettingsAlertDialogTest { rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT)) } - composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed() + composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed().assertIsEnabled() + } + + @Test + fun dismissButton_disabled() { + setAndOpenDialog { + rememberAlertDialogPresenter( + dismissButton = AlertDialogButton(text = DISMISS_TEXT, enabled = false) + ) + } + + composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed().assertIsNotEnabled() } @Test diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt index 977615b55a6a..f95cfc3191c8 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt @@ -30,22 +30,24 @@ import com.android.settingslib.spaprivileged.model.enterprise.rememberRestricted @Composable fun MoreOptionsScope.RestrictedMenuItem( text: String, + enabled: Boolean = true, restrictions: Restrictions, onClick: () -> Unit, ) { - RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl) + RestrictedMenuItemImpl(text, enabled, restrictions, onClick, ::RestrictionsProviderImpl) } @VisibleForTesting @Composable internal fun MoreOptionsScope.RestrictedMenuItemImpl( text: String, + enabled: Boolean = true, restrictions: Restrictions, onClick: () -> Unit, restrictionsProviderFactory: RestrictionsProviderFactory, ) { val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value - MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) { + MenuItem(text = text, enabled = enabled && restrictedMode !== BaseUserRestricted) { when (restrictedMode) { is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent() is BlockedByEcm -> restrictedMode.showRestrictedSettingsDetails() diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt index 556adc750763..4068bceb1475 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt @@ -49,6 +49,15 @@ class RestrictedMenuItemTest { private var menuItemOnClickIsCalled = false @Test + fun whenDisabled() { + val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) + + setContent(restrictions, enabled = false) + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsNotEnabled() + } + + @Test fun whenRestrictionsKeysIsEmpty_enabled() { val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) @@ -153,13 +162,14 @@ class RestrictedMenuItemTest { assertThat(menuItemOnClickIsCalled).isFalse() } - private fun setContent(restrictions: Restrictions) { + private fun setContent(restrictions: Restrictions, enabled: Boolean = true) { val fakeMoreOptionsScope = object : MoreOptionsScope() { override fun dismiss() {} } composeTestRule.setContent { fakeMoreOptionsScope.RestrictedMenuItemImpl( text = TEXT, + enabled = enabled, restrictions = restrictions, onClick = { menuItemOnClickIsCalled = true }, restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider }, diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 5e8060423eed..6b6f803e19b0 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> battery."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Regs: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Links <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Regs <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktief"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gestoor"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktief (net links)"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index eb1a36529e51..6f494f037bae 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"ግ፦ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>፣ ቀ፦ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> ባትሪ።"</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ግራ፦ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ባትሪ"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ቀኝ፦ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ባትሪ"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"ግራ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"ቀኝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ንቁ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ተቀምጧል"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ገቢር (ግራ ብቻ)"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index e64a7427f562..d02164704884 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -237,7 +237,7 @@ <string name="choose_profile" msgid="343803890897657450">"প্ৰ’ফাইল বাছনি কৰক"</string> <string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string> <string name="category_work" msgid="4014193632325996115">"কৰ্মস্থান-সম্পৰ্কীয়"</string> - <string name="category_private" msgid="4244892185452788977">"ব্যক্তিগত"</string> + <string name="category_private" msgid="4244892185452788977">"প্ৰাইভেট"</string> <string name="category_clone" msgid="1554511758987195974">"ক্ল’ন"</string> <string name="development_settings_title" msgid="140296922921597393">"বিকাশকৰ্তাৰ বিকল্পসমূহ"</string> <string name="development_settings_enable" msgid="4285094651288242183">"বিকাশকৰ্তা বিষয়ক বিকল্পসমূহ সক্ষম কৰক"</string> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 23bfcb5659bd..b7dda834f946 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batareya."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Sol <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Sağ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Yadda saxlandı"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiv (yalnız sol)"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 71b7f6e98ce1..0e4206633fb1 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktivan"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Sačuvano"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktivno (samo levo)"</string> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index 61ed0a19d908..a72322350b4c 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (левы навушнік), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (правы навушнік)."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (левы навушнік)"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (правы навушнік)"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Левы: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Правы: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Уключана"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Захавана"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Выкарыстоўваецца (толькі левы навушнік)"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 0515ed9ceff4..54fef5f1183a 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Л: батерия – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Д: батерия – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"За ляво ухо. Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"За дясно ухо. Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Вляво: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Вдясно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активно"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Запазено"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Активно (само лявото)"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index fea8137489b2..1533a1961cc1 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"বাঁদিক: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, ডানদিক: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> ব্যাটারি।"</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"বাঁদিক: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ব্যাটারি"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ডানদিক: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ব্যাটারি"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"বাঁদিকের হেডসেটে <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> চার্জ আছে"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"ডানদিকের হেডসেটে <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> চার্জ আছে"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"চালু আছে"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"সেভ করা আছে"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"চালু আছে (শুধু বাঁদিক)"</string> @@ -239,7 +237,7 @@ <string name="choose_profile" msgid="343803890897657450">"প্রোফাইল বেছে নিন"</string> <string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string> <string name="category_work" msgid="4014193632325996115">"অফিস"</string> - <string name="category_private" msgid="4244892185452788977">"ব্যক্তিগত"</string> + <string name="category_private" msgid="4244892185452788977">"প্রাইভেট"</string> <string name="category_clone" msgid="1554511758987195974">"ক্লোন"</string> <string name="development_settings_title" msgid="140296922921597393">"ডেভেলপার বিকল্প"</string> <string name="development_settings_enable" msgid="4285094651288242183">"ডেভেলপার বিকল্প সক্ষম করুন"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 7dce81a252c2..cc67420f5ee6 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Esquerre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Dret: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Esquerre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Dret: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Actiu"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Desat"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Actiu (només l\'esquerre)"</string> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 20ff455a442d..194c616b8ca5 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -237,7 +237,7 @@ <string name="choose_profile" msgid="343803890897657450">"Vyberte profil"</string> <string name="category_personal" msgid="6236798763159385225">"Osobní"</string> <string name="category_work" msgid="4014193632325996115">"Pracovní"</string> - <string name="category_private" msgid="4244892185452788977">"Soukromé"</string> + <string name="category_private" msgid="4244892185452788977">"Soukromý"</string> <string name="category_clone" msgid="1554511758987195974">"Klon"</string> <string name="development_settings_title" msgid="140296922921597393">"Pro vývojáře"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Aktivovat možnosti pro vývojáře"</string> @@ -287,7 +287,7 @@ <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Povolit odemknutí zavaděče"</string> <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"Povolit odemknutí OEM?"</string> <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"UPOZORNĚNÍ: Pokud bude toto nastavení zapnuto, nebudou v tomto zařízení fungovat funkce ochrany zařízení."</string> - <string name="mock_location_app" msgid="6269380172542248304">"Vybrat aplikaci k simulování polohy"</string> + <string name="mock_location_app" msgid="6269380172542248304">"Vybrat aplikaci k simulování polohy"</string> <string name="mock_location_app_not_set" msgid="6972032787262831155">"Aplikace k simulování polohy není nastavena"</string> <string name="mock_location_app_set" msgid="4706722469342913843">"Aplikace k simulování polohy: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="6829757985772659599">"Sítě"</string> @@ -602,7 +602,7 @@ <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Pro tohoto uživatele nejsou k dispozici žádná sdílená data."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Při načítání sdílených dat došlo k chybě. Zkuste to znovu."</string> <string name="blob_id_text" msgid="8680078988996308061">"ID sdílených dat: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> - <string name="blob_expires_text" msgid="7882727111491739331">"Platnost vyprší <xliff:g id="DATE">%s</xliff:g>"</string> + <string name="blob_expires_text" msgid="7882727111491739331">"Platnost skončí <xliff:g id="DATE">%s</xliff:g>"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"Při mazání sdílených dat došlo k chybě."</string> <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"Pro tato sdílená data nejsou k dispozici žádné smlouvy. Chcete je smazat?"</string> <string name="accessor_info_title" msgid="8289823651512477787">"Aplikace, které sdílejí data"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index cfa64c00da32..f0fb4df441b6 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Akku links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Akku rechts: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Akku links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Akku rechts: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Links – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Rechts – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gespeichert"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiv (nur links)"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index bd1b848b01c4..94d4297d7f57 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Α: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Δ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> μπαταρία."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Αριστερά: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> μπαταρία"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Δεξιά: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> μπαταρία"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Αριστερό <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Δεξί <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ενεργό"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Αποθηκεύτηκε"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ενεργό (μόνο το αριστερό)"</string> @@ -237,9 +235,9 @@ <item msgid="6946761421234586000">"400%"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"Επιλογή προφίλ"</string> - <string name="category_personal" msgid="6236798763159385225">"Προσωπικό"</string> + <string name="category_personal" msgid="6236798763159385225">"Προσωπικός"</string> <string name="category_work" msgid="4014193632325996115">"Εργασίας"</string> - <string name="category_private" msgid="4244892185452788977">"Ιδιωτικό"</string> + <string name="category_private" msgid="4244892185452788977">"Ιδιωτικός"</string> <string name="category_clone" msgid="1554511758987195974">"Κλωνοποίηση"</string> <string name="development_settings_title" msgid="140296922921597393">"Επιλογές για προγραμματιστές"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Ενεργοποίηση επιλογών για προγραμματιστές"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 0167ec1c66e9..b96dd33e35c0 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"I: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>; D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Activo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Guardado"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Activo (solo izquierdo)"</string> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index 0899d5aaff1c..3c63119497df 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Izquierda <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Derecha <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Activo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Guardado"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Activo (solo izquierdo)"</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 6be5f6c9b156..25f002a29c49 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>. R aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Ezkerreko aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Eskuineko aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Ezkerrekoa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Eskuinekoa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktibo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gordeta"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktibo (ezkerrekoa soilik)"</string> @@ -239,7 +237,7 @@ <string name="choose_profile" msgid="343803890897657450">"Aukeratu profila"</string> <string name="category_personal" msgid="6236798763159385225">"Pertsonalak"</string> <string name="category_work" msgid="4014193632325996115">"Lanekoak"</string> - <string name="category_private" msgid="4244892185452788977">"Pribatua"</string> + <string name="category_private" msgid="4244892185452788977">"Pribatuak"</string> <string name="category_clone" msgid="1554511758987195974">"Klonatu"</string> <string name="development_settings_title" msgid="140296922921597393">"Garatzaileentzako aukerak"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Gaitu garatzaileen aukerak"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index a5fe65de6a1f..399e3968acf2 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>، باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"چپ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"راست <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"فعال"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ذخیرهشده"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"فعال (فقط چپ)"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 03a6d432942a..d2afaac3eed6 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, O: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> virtaa."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Vasen: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Oikea: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Vasen <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Oikea <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiivinen"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Tallennettu"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiivinen (vain vasen)"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 7d2bea02c281..5f4480cd58e4 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Gauche : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batterie, droit : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batterie."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Gauche : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Droit : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Gauche (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>)"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Droit (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>)"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Actif"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Enregistré"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Actif (gauche uniquement)"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index cc03004af261..874a153febda 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -492,7 +492,7 @@ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> para completar a carga)"</string> <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> (carga optimizada)"</string> <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> (cargando)"</string> - <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - Completa á/s <xliff:g id="TIME">%3$s</xliff:g>"</string> + <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - Completarase á/s <xliff:g id="TIME">%3$s</xliff:g>"</string> <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga completa á/s <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Carga completa á/s <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Completa á/s <xliff:g id="TIME">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index a5bf093be445..07eda9a27336 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"ડાબી બાજુ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, જમણી બાજુ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> બૅટરી."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ડાબી બાજુ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> બૅટરી"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"જમણી બાજુ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> બૅટરી"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"ડાબી બાજુ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> બૅટરી"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"જમણી બાજુ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> બૅટરી"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"સક્રિય"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"સાચવેલું"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ચાલુ છે (માત્ર ડાબી બાજુ)"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 2aee1bca391a..f66499fba432 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -237,7 +237,7 @@ <string name="choose_profile" msgid="343803890897657450">"प्रोफ़ाइल चुनें"</string> <string name="category_personal" msgid="6236798763159385225">"निजी"</string> <string name="category_work" msgid="4014193632325996115">"वर्क"</string> - <string name="category_private" msgid="4244892185452788977">"निजी"</string> + <string name="category_private" msgid="4244892185452788977">"प्राइवेट"</string> <string name="category_clone" msgid="1554511758987195974">"क्लोन"</string> <string name="development_settings_title" msgid="140296922921597393">"डेवलपर के लिए सेटिंग और टूल"</string> <string name="development_settings_enable" msgid="4285094651288242183">"डेवलपर के लिए सेटिंग और टूल चालू करें"</string> @@ -708,7 +708,7 @@ <string name="physical_keyboard_title" msgid="4811935435315835220">"फ़िज़िकल कीबोर्ड"</string> <string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"कीबोर्ड का लेआउट चुनें"</string> <string name="keyboard_layout_default_label" msgid="1997292217218546957">"डिफ़ॉल्ट"</string> - <string name="turn_screen_on_title" msgid="2662312432042116026">"इन ऐप के पास स्क्रीन को चालू करने का कंट्रोल है"</string> + <string name="turn_screen_on_title" msgid="2662312432042116026">"इस ऐप के पास स्क्रीन को चालू करने का कंट्रोल है"</string> <string name="allow_turn_screen_on" msgid="6194845766392742639">"स्क्रीन चालू करने की अनुमति दें"</string> <string name="allow_turn_screen_on_description" msgid="43834403291575164">"ऐप्लिकेशन को स्क्रीन चालू करने की अनुमति दें. ऐसा करने पर, ऐप्लिकेशन आपकी अनुमति लिए बिना भी, जब चाहे स्क्रीन चालू कर सकता है."</string> <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर ब्रॉडकास्ट करना रोकें?"</string> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 0271b09cefde..8ce90ec1e4b0 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Lijeva strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Desna strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Lijevo <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Desno <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktivan"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Spremljeno"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktivno (samo lijevo)"</string> @@ -494,7 +492,7 @@ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do napunjenosti"</string> <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje se optimizira"</string> <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje"</string> - <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATUS">%2$s</xliff:g> – napunjeno do <xliff:g id="TIME">%3$s</xliff:g>"</string> + <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATUS">%2$s</xliff:g> – bit će pun do <xliff:g id="TIME">%3$s</xliff:g>"</string> <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> – potpuno napunjeno do <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Potpuno napunjeno do <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Napunjeno do <xliff:g id="TIME">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 5c066244c057..fb142f62f74d 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Akkumulátorok töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (bal) és <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (jobb)."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (bal)."</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (jobb)."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Bal: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Jobb: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktív"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Mentve"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktív (csak bal)"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 805284ee9b78..e7cbf2d3b3eb 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Ձախ ականջակալի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, աջ ականջակալի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>։"</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Ձախ ականջակալի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Աջ ականջակալի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Ձախը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Աջը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ակտիվ է"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Պահված է"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ակտիվ է (միայն ձախ)"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 6b03ae5aeab7..82a390e37c80 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Baterai L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Kiri: Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Kanan: Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Kiri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Kanan <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktif"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Disimpan"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktif (hanya kiri)"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index d89da7736917..47cdcca2bbe4 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> rafhlöðuhleðsla."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Vinstri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Hægri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Vinstri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Hægri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Virkt"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Vistað"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Kveikt (eingöngu vinstra)"</string> @@ -237,7 +235,7 @@ <item msgid="6946761421234586000">"400%"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"Veldu snið"</string> - <string name="category_personal" msgid="6236798763159385225">"Persónulegt"</string> + <string name="category_personal" msgid="6236798763159385225">"Einkasnið"</string> <string name="category_work" msgid="4014193632325996115">"Vinna"</string> <string name="category_private" msgid="4244892185452788977">"Lokað"</string> <string name="category_clone" msgid="1554511758987195974">"Afrit"</string> @@ -497,7 +495,7 @@ <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - Fullt kl. <xliff:g id="TIME">%3$s</xliff:g>"</string> <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> - Fullhlaðið kl. <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Fullhlaðið kl. <xliff:g id="TIME">%1$s</xliff:g>"</string> - <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Fullt kl. <xliff:g id="TIME">%1$s</xliff:g>"</string> + <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Fullhlaðin kl. <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hröð hleðsla"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 92f9554a0fd2..3714593b3e3b 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"S: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> di batteria. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> di batteria."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Sinistro: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Destro: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Sinistra: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Destra: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Attivo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Dispositivo salvato"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Attivo (solo sinistro)"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 5eb9170b2e61..c2f4a8f5f8cb 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"סוללה בצד שמאל: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, סוללה בצד ימין: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"סוללה בצד שמאל: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"סוללה בצד ימין: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"אוזנייה שמאלית: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"אוזנייה ימנית: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"פעיל"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"בוצעה שמירה"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"פעיל (שמאל בלבד)"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 0efe609ef113..a1ea07011f39 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Сол жақ: батарея зарядының деңгейі – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>. Оң жақ: батарея зарядының деңгейі — <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Сол жақ: батарея зарядының деңгейі – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Оң жақ: батарея зарядының деңгейі – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Сол: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Оң: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Қосулы"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Сақталған"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Істеп тұр (тек сол жағы)"</string> @@ -494,7 +492,7 @@ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: толық зарядталуға <xliff:g id="TIME">%2$s</xliff:g> қалды"</string> <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядтау оңтайландырылды"</string> <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зарядталып жатыр"</string> - <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - Заряд толуына қалған уақыт: <xliff:g id="TIME">%3$s</xliff:g>"</string> + <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - Зарядталып болады: <xliff:g id="TIME">%3$s</xliff:g>"</string> <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> - Толық заряд алуға қалған уақыт: <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Толық заряд алуға қалған уақыт: <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Заряд толуына қалған уақыт: <xliff:g id="TIME">%1$s</xliff:g>"</string> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 0882327d409f..431bdf7e5265 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"ឆ្វេង៖ ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> ស្ដាំ៖ ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>។"</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ឆ្វេង៖ ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ស្ដាំ៖ ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"ឆ្វេង <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"ស្ដាំ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"សកម្ម"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"បានរក្សាទុក"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"សកម្ម (ខាងឆ្វេងប៉ុណ្ណោះ)"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index d47c76b7b801..13819033d166 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"ಎಡ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, ಬಲ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> ಬ್ಯಾಟರಿ ಮಟ್ಟ."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ಎಡ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ಬ್ಯಾಟರಿ ಮಟ್ಟ"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ಬಲ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ಬ್ಯಾಟರಿ ಮಟ್ಟ"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"ಎಡ ಭಾಗದ ಬ್ಯಾಟರಿ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"ಬಲ ಭಾಗದ ಬ್ಯಾಟರಿ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ಸಕ್ರಿಯ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ಸಕ್ರಿಯವಾಗಿದೆ (ಎಡಕಿವಿಯ ಸಾಧನ ಮಾತ್ರ)"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 51c84758c576..9cb770f7b5ff 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"배터리는 왼쪽 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, 오른쪽 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>입니다."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"왼쪽 배터리는 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>입니다."</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"오른쪽 배터리는 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>입니다."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"왼쪽 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"오른쪽 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"활성"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"저장됨"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"활성(왼쪽만)"</string> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 0560a81a6142..64180a995937 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Батарея: L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Сол кулак – батареянын деңгээли: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Оң кулак – батареянын деңгээли: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Сол: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Оң: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Жигердүү"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Сакталган"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Иштеп жатат (сол тарап гана)"</string> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index 201c7a085c31..a8c3f5a41f46 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Akumulatora uzlādes līmenis labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Kreisā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Labā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktīvs"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Saglabāta"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ierīce aktīva (tikai kreisā auss)"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index a72a72e8c896..b2456d3f9b53 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Л: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> батерија, Д: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батерија."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Лево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Десно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Одлево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Оддесно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активен"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Зачувано"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Активно (само лево)"</string> @@ -237,9 +235,9 @@ <item msgid="6946761421234586000">"400 %"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"Изберете профил"</string> - <string name="category_personal" msgid="6236798763159385225">"Личен"</string> + <string name="category_personal" msgid="6236798763159385225">"Лично"</string> <string name="category_work" msgid="4014193632325996115">"Работа"</string> - <string name="category_private" msgid="4244892185452788977">"Приватен"</string> + <string name="category_private" msgid="4244892185452788977">"Приватно"</string> <string name="category_clone" msgid="1554511758987195974">"Клон"</string> <string name="development_settings_title" msgid="140296922921597393">"Програмерски опции"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Овозможете ги програмерските опции"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index 41afe435c162..436326e8334f 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"З: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Б: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батарей."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Зүүн: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Баруун: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Зүүн тал <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Баруун тал <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Идэвхтэй"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Хадгалсан"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Идэвхтэй (зөвхөн зүүн тал)"</string> @@ -239,7 +237,7 @@ <string name="choose_profile" msgid="343803890897657450">"Профайл сонгох"</string> <string name="category_personal" msgid="6236798763159385225">"Хувийн"</string> <string name="category_work" msgid="4014193632325996115">"Ажил"</string> - <string name="category_private" msgid="4244892185452788977">"Хувийн"</string> + <string name="category_private" msgid="4244892185452788977">"Хаалттай"</string> <string name="category_clone" msgid="1554511758987195974">"Клон"</string> <string name="development_settings_title" msgid="140296922921597393">"Хөгжүүлэгчийн тохиргоо"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Хөгжүүлэгчийн сонголтыг идэвхжүүлэх"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index bd1659c5f99a..1a672c39a42c 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Venstre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Høyre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Venstre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Høyre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Lagret"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiv (bare venstre)"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index 8fc58d1d504d..562a55879bdb 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"ବାମ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, ଡାହାଣ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> ବେଟେରୀ।"</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ବାମ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ଡାହାଣ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"ବାମ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"ଡାହାଣ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ସକ୍ରିୟ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ସେଭ କରାଯାଇଛି"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ସକ୍ରିୟ (କେବଳ ବାମ)"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 36a52b29388b..72e77609f9b1 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"ਖੱਬੇ ਪਾਸੇ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, ਸੱਜੇ ਪਾਸੇ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> ਬੈਟਰੀ।"</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ਖੱਬੇ ਪਾਸੇ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ਬੈਟਰੀ"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ਸੱਜੇ ਪਾਸੇ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ਬੈਟਰੀ"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"ਖੱਬੇ ਪਾਸੇ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"ਸੱਜੇ ਪਾਸੇ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ਕਿਰਿਆਸ਼ੀਲ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ਕਿਰਿਆਸ਼ੀਲ (ਸਿਰਫ਼ ਖੱਬਾ)"</string> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index da68889ef4e6..3b6d758f8c77 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -234,7 +234,7 @@ <item msgid="4446831566506165093">"350%"</item> <item msgid="6946761421234586000">"400%"</item> </string-array> - <string name="choose_profile" msgid="343803890897657450">"Wybierz profil"</string> + <string name="choose_profile" msgid="343803890897657450">"Wybierz profil konta"</string> <string name="category_personal" msgid="6236798763159385225">"Osobiste"</string> <string name="category_work" msgid="4014193632325996115">"Służbowe"</string> <string name="category_private" msgid="4244892185452788977">"Prywatne"</string> @@ -495,7 +495,7 @@ <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATUS">%2$s</xliff:g> – Bateria będzie pełna do <xliff:g id="TIME">%3$s</xliff:g>"</string> <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> – Pełne naładowanie do <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Pełne naładowanie do <xliff:g id="TIME">%1$s</xliff:g>"</string> - <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Bateria będzie pełna do <xliff:g id="TIME">%1$s</xliff:g>"</string> + <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Bateria będzie w pełni naładowana do <xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 2676ff62cb16..d47a1f4d3b5a 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ativo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Salvo"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ativo (apenas o esquerdo)"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 2676ff62cb16..d47a1f4d3b5a 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ativo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Salvo"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ativo (apenas o esquerdo)"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 4c97658adab6..1c76fdd9191a 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, dreapta: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Nivelul bateriei din dreapta:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Dreapta: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Activ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Salvat"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Activ (numai stânga)"</string> @@ -198,7 +196,7 @@ <string name="launch_defaults_some" msgid="3631650616557252926">"Unele valori prestabilite sunt configurate"</string> <string name="launch_defaults_none" msgid="8049374306261262709">"Nu este configurată nicio valoare prestabilită"</string> <string name="tts_settings" msgid="8130616705989351312">"Setări redare vocală a textului"</string> - <string name="tts_settings_title" msgid="7602210956640483039">"Rezultatul redării vocale a textului"</string> + <string name="tts_settings_title" msgid="7602210956640483039">"Setări pentru redarea vocală a textului"</string> <string name="tts_default_rate_title" msgid="3964187817364304022">"Ritmul vorbirii"</string> <string name="tts_default_rate_summary" msgid="3781937042151716987">"Viteza cu care este vorbit textul"</string> <string name="tts_default_pitch_title" msgid="6988592215554485479">"Înălțime"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 505a1ecd0a15..2972e611d565 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (Л), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (П)."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (Л)"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (П)"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Левый <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Правый <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активно"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Сохранено"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Используется (только левый)"</string> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index 6e4b5f91924a..9f1aca6d7b7c 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"බැටරිය ව: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, ද: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"වම: බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"දකුණ: බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"වම <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"දකුණ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ක්රියාකාරී"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"සුරැකිණි"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"සක්රිය (වම පමණි)"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 64c111304a8d..db8301a52e5c 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Ľ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batérie."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Ľavá strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batérie"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Pravá strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batérie"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Ľavé: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Pravé: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktívne"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Uložené"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktívne (iba ľavé)"</string> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 0288ec2dbc9c..c33e425269cb 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> bateri, djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> bateri."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Të ruajtura"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktive (vetëm majtas)"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 927b8e4dff92..fa94c2ef0648 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Лево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, десно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батерије."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Лево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерије"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Десно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерије"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Лево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Десно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активан"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Сачувано"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Активно (само лево)"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 7c6a83281d86..c4677beb6e11 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Vänster: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Höger: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Vänster: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Höger <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Sparad"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiv (endast vänster)"</string> @@ -237,7 +235,7 @@ <item msgid="6946761421234586000">"400 %"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"Välj profil"</string> - <string name="category_personal" msgid="6236798763159385225">"Privat"</string> + <string name="category_personal" msgid="6236798763159385225">"Personlig"</string> <string name="category_work" msgid="4014193632325996115">"Jobb"</string> <string name="category_private" msgid="4244892185452788977">"Privat"</string> <string name="category_clone" msgid="1554511758987195974">"Klon"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 4b67011c7711..ff62ba0d29a4 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"இடது பேட்டரி: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, வலது பேட்டரி: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"இடது: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> பேட்டரி"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"வலது: - <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> பேட்டரி"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"இடதுபுறம்: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"வலதுபுறம்: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"செயலில் உள்ளது"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"சேமிக்கப்பட்டது"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"செயலில் உள்ளது (இடதுபுறம் மட்டும்)"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index afda7cbd5ade..014ba26e2f3b 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -235,7 +235,7 @@ <item msgid="6946761421234586000">"400%"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"เลือกโปรไฟล์"</string> - <string name="category_personal" msgid="6236798763159385225">"ส่วนตัว"</string> + <string name="category_personal" msgid="6236798763159385225">"ส่วนบุคคล"</string> <string name="category_work" msgid="4014193632325996115">"งาน"</string> <string name="category_private" msgid="4244892185452788977">"ส่วนตัว"</string> <string name="category_clone" msgid="1554511758987195974">"โคลน"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 97d20bf7c5ee..6aea659292c3 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pil seviyesi."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Sol <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Sağ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Etkin"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Kaydedildi"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Etkin (yalnızca sol taraf)"</string> @@ -239,7 +237,7 @@ <string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string> <string name="category_personal" msgid="6236798763159385225">"Kişisel"</string> <string name="category_work" msgid="4014193632325996115">"İş"</string> - <string name="category_private" msgid="4244892185452788977">"Gizli"</string> + <string name="category_private" msgid="4244892185452788977">"Özel"</string> <string name="category_clone" msgid="1554511758987195974">"Klon"</string> <string name="development_settings_title" msgid="140296922921597393">"Geliştirici seçenekleri"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Geliştirici seçeneklerini etkinleştir"</string> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index ab1e7f66a4c6..0400e0e321bb 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Лівий: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, правий: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> заряду акумулятора."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Лівий: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> заряду акумулятора"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Правий: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> заряду акумулятора"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Ліва частина: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Права частина: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активовано"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Збережено"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Активовано (лише лівий)"</string> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index f0a89b30e580..f285086d2705 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (L), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (R)."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (chap)"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (oʻng)."</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Chapda: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Oʻngda: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Faol"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Saqlangan"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Faol (faqat chap)"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index d85449edbc18..d7cc91e4ad03 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -492,10 +492,10 @@ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là pin đầy"</string> <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Quá trình sạc được tối ưu hoá"</string> <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đang sạc"</string> - <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATUS">%2$s</xliff:g> – Pin sẽ đầy vào <xliff:g id="TIME">%3$s</xliff:g>"</string> - <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> – Pin sẽ đầy vào <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Pin sẽ đầy vào <xliff:g id="TIME">%1$s</xliff:g>"</string> - <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Pin sẽ đầy vào <xliff:g id="TIME">%1$s</xliff:g>"</string> + <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATUS">%2$s</xliff:g> – Đến <xliff:g id="TIME">%3$s</xliff:g> pin sẽ đầy"</string> + <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đến <xliff:g id="TIME">%2$s</xliff:g> pin sẽ đầy"</string> + <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Đến <xliff:g id="TIME">%1$s</xliff:g> pin sẽ đầy"</string> + <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Đến <xliff:g id="TIME">%1$s</xliff:g> pin sẽ đầy"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Đang sạc nhanh"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 20fd65197e88..7a355b9bdc54 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"左侧电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>,右侧电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>。"</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"左侧电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"右侧电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"左耳机电池电量 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"右耳机电池电量 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"使用中"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"已保存的设备"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"使用中(仅左耳助听器)"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index 556c0af9a877..d09a2811a504 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -495,7 +495,7 @@ <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - 在 <xliff:g id="TIME">%3$s</xliff:g>前充滿電"</string> <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> • 在 <xliff:g id="TIME">%2$s</xliff:g>前充滿電"</string> <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"在 <xliff:g id="TIME">%1$s</xliff:g>前充滿電"</string> - <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"在 <xliff:g id="TIME">%1$s</xliff:g>前充滿電"</string> + <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"<xliff:g id="TIME">%1$s</xliff:g> 前充滿電"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 063d148157a1..0a0fdfff114a 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -103,10 +103,8 @@ <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> ibhethri."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Kwesobunxele: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ibhethri"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Kwesokudla: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ibhethri"</string> - <!-- no translation found for tv_bluetooth_battery_level_untethered_left (337629670583744410) --> - <skip /> - <!-- no translation found for tv_bluetooth_battery_level_untethered_right (8610019317279155595) --> - <skip /> + <string name="tv_bluetooth_battery_level_untethered_left" msgid="337629670583744410">"Kwesokunxele <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="tv_bluetooth_battery_level_untethered_right" msgid="8610019317279155595">"Kwesokudla <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Iyasebenza"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Ilondoloziwe"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Iyasebenza (ngakwesokunxele kuphela)"</string> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index ab049042b5f9..470cdeea149b 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -32,7 +32,7 @@ <!-- Usage graph dimens --> <dimen name="usage_graph_margin_top_bottom">9dp</dimen> - <dimen name="usage_graph_labels_width">56dp</dimen> + <dimen name="usage_graph_labels_width">60dp</dimen> <dimen name="usage_graph_divider_size">1dp</dimen> diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java index 1040ac6bc860..c5e86b4fb280 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java @@ -210,7 +210,7 @@ public class PowerAllowlistBackend { mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled( pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED, true, ActivityManager.RESTRICTION_REASON_USER, - "settings", 0); + "settings", ActivityManager.RESTRICTION_SOURCE_USER, 0); } } @@ -251,7 +251,7 @@ public class PowerAllowlistBackend { mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled( pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED, false, ActivityManager.RESTRICTION_REASON_USER, - "settings", 0); + "settings", ActivityManager.RESTRICTION_SOURCE_USER, 0L); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt b/packages/SettingsLib/src/com/android/settingslib/view/accessibility/data/repository/CaptioningRepository.kt index 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/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt index 869fb7f4043c..70811951c9b7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt @@ -81,7 +81,7 @@ class LocalMediaRepositoryImpl( localMediaManager.unregisterCallback(callback) } } - .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0) + .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 0) override val currentConnectedDevice: StateFlow<MediaDevice?> = merge(devicesChanges, mediaDevicesUpdates) @@ -89,8 +89,8 @@ class LocalMediaRepositoryImpl( .onStart { emit(localMediaManager.currentConnectedDevice) } .stateIn( coroutineScope, - SharingStarted.WhileSubscribed(), - localMediaManager.currentConnectedDevice + SharingStarted.Eagerly, + localMediaManager.currentConnectedDevice, ) private sealed interface DevicesUpdate { 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/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index e125083488ed..397fab129e39 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -64,6 +64,8 @@ android_robolectric_test { timeout: 36000, }, upstream: true, + + strict_mode: false, } java_genrule { diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java index ca1e4c10d339..e4898daf3cbf 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java @@ -27,11 +27,11 @@ import android.support.test.uiautomator.UiDevice; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile; +import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile; import com.android.bedstead.harrier.BedsteadJUnit4; import com.android.bedstead.harrier.DeviceState; -import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile; import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser; -import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile; import com.android.bedstead.harrier.annotations.RequireFeature; import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser; import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser; 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/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 55edff6d9518..d201071620e4 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -81,3 +81,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "hearing_devices_dialog_related_tools" + namespace: "accessibility" + description: "Shows the related tools for hearing devices dialog." + bug: "341648471" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 163c8493e4d9..63a52d624ca7 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -177,7 +177,7 @@ flag { } flag { - name: "notification_throttle_hun" + name: "notification_avalanche_throttle_hun" namespace: "systemui" description: "During notification avalanche, throttle HUNs showing in fast succession." bug: "307288824" @@ -960,10 +960,13 @@ flag { } flag { - name: "media_controls_user_initiated_dismiss" + name: "media_controls_user_initiated_deleteintent" namespace: "systemui" description: "Only dismiss media notifications when the control was removed by the user." bug: "335875159" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -987,6 +990,20 @@ flag { } flag { + name: "glanceable_hub_fullscreen_swipe" + namespace: "systemui" + description: "Increase swipe area for gestures to bring in glanceable hub" + bug: "339665673" +} + +flag { + name: "glanceable_hub_shortcut_button" + namespace: "systemui" + description: "Shows a button over the dream and lock screen to open the glanceable hub" + bug: "339667383" +} + +flag { name: "glanceable_hub_gesture_handle" namespace: "systemui" description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub" @@ -1009,3 +1026,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "notification_media_manager_background_execution" + namespace: "systemui" + description: "Decide whether to execute binder calls in background thread" + bug: "336612071" + metadata { + purpose: PURPOSE_BUGFIX + } +} 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/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index b1240252796f..978943ae7f7c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -179,8 +179,15 @@ class TextAnimator( private val fontVariationUtils = FontVariationUtils() - fun updateLayout(layout: Layout) { + fun updateLayout(layout: Layout, textSize: Float = -1f) { textInterpolator.layout = layout + + if (textSize >= 0) { + textInterpolator.targetPaint.textSize = textSize + textInterpolator.basePaint.textSize = textSize + textInterpolator.onTargetPaintModified() + textInterpolator.onBasePaintModified() + } } fun isRunning(): Boolean { 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/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt deleted file mode 100644 index dff8753fd880..000000000000 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt +++ /dev/null @@ -1,96 +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.compose.ui.platform - -import android.content.Context -import android.content.res.Configuration -import android.util.AttributeSet -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.platform.AbstractComposeView - -/** - * A ComposeView that recreates its composition if the display size or font scale was changed. - * - * TODO(b/317317814): Remove this workaround. - */ -class DensityAwareComposeView(context: Context) : OpenComposeView(context) { - private var lastDensityDpi: Int = -1 - private var lastFontScale: Float = -1f - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - - val configuration = context.resources.configuration - lastDensityDpi = configuration.densityDpi - lastFontScale = configuration.fontScale - } - - override fun dispatchConfigurationChanged(newConfig: Configuration) { - super.dispatchConfigurationChanged(newConfig) - - // If the density or font scale changed, we dispose then recreate the composition. Note that - // we do this here after dispatching the new configuration to children (instead of doing - // this in onConfigurationChanged()) because the new configuration should first be - // dispatched to the AndroidComposeView that holds the current density before we recreate - // the composition. - val densityDpi = newConfig.densityDpi - val fontScale = newConfig.fontScale - if (densityDpi != lastDensityDpi || fontScale != lastFontScale) { - lastDensityDpi = densityDpi - lastFontScale = fontScale - - disposeComposition() - if (isAttachedToWindow) { - createComposition() - } - } - } -} - -/** A fork of [androidx.compose.ui.platform.ComposeView] that is open and can be subclassed. */ -open class OpenComposeView -internal constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : - AbstractComposeView(context, attrs, defStyleAttr) { - - private val content = mutableStateOf<(@Composable () -> Unit)?>(null) - - @Suppress("RedundantVisibilityModifier") - protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false - - @Composable - override fun Content() { - content.value?.invoke() - } - - override fun getAccessibilityClassName(): CharSequence { - return javaClass.name - } - - /** - * Set the Jetpack Compose UI content for this view. Initial composition will occur when the - * view becomes attached to a window or when [createComposition] is called, whichever comes - * first. - */ - fun setContent(content: @Composable () -> Unit) { - shouldCreateCompositionOnAttachedToWindow = true - this.content.value = content - if (isAttachedToWindow) { - createComposition() - } - } -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index c19c08e09349..b8f9ca82f072 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -66,6 +66,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -137,7 +138,7 @@ fun BouncerContent( // Despite the keyboard only being part of the password bouncer, adding it at this level is // both necessary to properly handle the keyboard in all layouts and harmless in cases when // the keyboard isn't used (like the PIN or pattern auth methods). - modifier = modifier.imePadding(), + modifier = modifier.imePadding().onKeyEvent(viewModel::onKeyEvent), ) { when (layout) { BouncerSceneLayout.STANDARD_BOUNCER -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index feb1f5b17bef..a90f82eda1af 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -21,6 +21,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.CommunalSwipeDetector +import com.android.compose.animation.scene.DefaultSwipeDetector import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher @@ -35,6 +37,7 @@ import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions import com.android.systemui.Flags +import com.android.systemui.Flags.glanceableHubFullscreenSwipe import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.ui.compose.extensions.allowGestures @@ -108,6 +111,8 @@ fun CommunalContainer( ) } + val detector = remember { CommunalSwipeDetector() } + DisposableEffect(state) { val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope) dataSourceDelegator.setDelegate(dataSource) @@ -121,13 +126,25 @@ fun CommunalContainer( onDispose { viewModel.setTransitionState(null) } } + val swipeSourceDetector = + if (glanceableHubFullscreenSwipe()) { + detector + } else { + FixedSizeEdgeDetector(dimensionResource(id = R.dimen.communal_gesture_initiation_width)) + } + + val swipeDetector = + if (glanceableHubFullscreenSwipe()) { + detector + } else { + DefaultSwipeDetector + } + SceneTransitionLayout( state = state, modifier = modifier.fillMaxSize(), - swipeSourceDetector = - FixedSizeEdgeDetector( - dimensionResource(id = R.dimen.communal_gesture_initiation_width) - ), + swipeSourceDetector = swipeSourceDetector, + swipeDetector = swipeDetector, ) { scene( CommunalScenes.Blank, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index cd27d5713c2d..9dd3d39cb040 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -23,6 +23,7 @@ import android.util.SizeF import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS import android.widget.FrameLayout +import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalAnimationApi @@ -162,7 +163,6 @@ fun CommunalHub( var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var toolbarSize: IntSize? by remember { mutableStateOf(null) } var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } - var isDraggingToRemove by remember { mutableStateOf(false) } val gridState = rememberLazyGridState() val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel) val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle() @@ -250,12 +250,11 @@ fun CommunalHub( contentOffset = contentOffset, setGridCoordinates = { gridCoordinates = it }, updateDragPositionForRemove = { offset -> - isDraggingToRemove = - isPointerWithinCoordinates( - offset = gridCoordinates?.let { it.positionInWindow() + offset }, - containerToCheck = removeButtonCoordinates - ) - isDraggingToRemove + isPointerWithinEnabledRemoveButton( + removeEnabled = removeButtonEnabled, + offset = gridCoordinates?.let { it.positionInWindow() + offset }, + containerToCheck = removeButtonCoordinates + ) }, gridState = gridState, contentListState = contentListState, @@ -640,11 +639,16 @@ private fun AnimatedVisibilityScope.ButtonToEditWidgets( enter = fadeIn( initialAlpha = 0f, - animationSpec = tween(durationMillis = 500, easing = LinearEasing) + animationSpec = tween(durationMillis = 83, easing = LinearEasing) ), exit = fadeOut( - animationSpec = tween(durationMillis = 500, easing = LinearEasing) + animationSpec = + tween( + durationMillis = 83, + delayMillis = 167, + easing = LinearEasing + ) ) ) .background(colors.secondary, RoundedCornerShape(50.dp)), @@ -658,7 +662,7 @@ private fun AnimatedVisibilityScope.ButtonToEditWidgets( animationSpec = tween( durationMillis = 167, - delayMillis = 500, + delayMillis = 83, easing = LinearEasing ) ), @@ -1195,11 +1199,13 @@ private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingIn * Check whether the pointer position that the item is being dragged at is within the coordinates of * the remove button in the toolbar. Returns true if the item is removable. */ -private fun isPointerWithinCoordinates( +@VisibleForTesting +fun isPointerWithinEnabledRemoveButton( + removeEnabled: Boolean, offset: Offset?, containerToCheck: LayoutCoordinates? ): Boolean { - if (offset == null || containerToCheck == null) { + if (!removeEnabled || offset == null || containerToCheck == null) { return false } val container = containerToCheck.boundsInWindow() 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/NotificationHeadsUpHeight.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationHeadsUpHeight.kt new file mode 100644 index 000000000000..75a565b2d2a0 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationHeadsUpHeight.kt @@ -0,0 +1,87 @@ +/* + * 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.notifications.ui.composable + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateMeasurement +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntOffset +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView + +/** + * Modify element, which updates the height to the height of current top heads up notification, or + * to 0 if there is none. + * + * @param view Notification stack scroll view + */ +fun Modifier.notificationHeadsUpHeight(view: NotificationScrollView) = + this then HeadsUpLayoutElement(view) + +private data class HeadsUpLayoutElement( + val view: NotificationScrollView, +) : ModifierNodeElement<HeadsUpLayoutNode>() { + + override fun create(): HeadsUpLayoutNode = HeadsUpLayoutNode(view) + + override fun update(node: HeadsUpLayoutNode) { + check(view == node.view) { "Trying to reuse the node with a new View." } + } +} + +private class HeadsUpLayoutNode(val view: NotificationScrollView) : + LayoutModifierNode, Modifier.Node() { + + private val headsUpHeightChangedListener = Runnable { invalidateMeasureIfAttached() } + + override fun onAttach() { + super.onAttach() + view.addHeadsUpHeightChangedListener(headsUpHeightChangedListener) + } + + override fun onDetach() { + super.onDetach() + view.removeHeadsUpHeightChangedListener(headsUpHeightChangedListener) + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + // TODO(b/339181697) make sure, that the row is already measured. + val contentHeight = view.topHeadsUpHeight + val placeable = + measurable.measure( + constraints.copy(minHeight = contentHeight, maxHeight = contentHeight) + ) + return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) } + } + + override fun toString(): String { + return "HeadsUpLayoutNode(view=$view)" + } + + fun invalidateMeasureIfAttached() { + if (isAttached) { + this.invalidateMeasurement() + } + } +} 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..ee3ffce27d51 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 @@ -67,7 +67,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope -import com.android.compose.modifiers.height import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius @@ -108,18 +107,17 @@ object Notifications { */ @Composable fun SceneScope.HeadsUpNotificationSpace( + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, isPeekFromBottom: Boolean = false, ) { - val headsUpHeight = viewModel.headsUpHeight.collectAsStateWithLifecycle() - Element( Notifications.Elements.HeadsUpNotificationPlaceholder, modifier = modifier - .height { headsUpHeight.value.roundToInt() } .fillMaxWidth() + .notificationHeadsUpHeight(stackScrollView) .debugBackground(viewModel, DEBUG_HUN_COLOR) .onGloballyPositioned { coordinates: LayoutCoordinates -> val boundsInWindow = coordinates.boundsInWindow() @@ -128,7 +126,8 @@ fun SceneScope.HeadsUpNotificationSpace( " size=${coordinates.size}" + " bounds=$boundsInWindow" } - viewModel.onHeadsUpTopChanged(boundsInWindow.top) + // Note: boundsInWindow doesn't scroll off the screen + stackScrollView.setHeadsUpTop(boundsInWindow.top) } ) { content {} @@ -152,6 +151,7 @@ fun SceneScope.ConstrainedNotificationStack( modifier = Modifier.fillMaxSize(), ) HeadsUpNotificationSpace( + stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.align(Alignment.TopCenter), ) @@ -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( @@ -357,7 +358,7 @@ fun SceneScope.NotificationScrollingStack( .onSizeChanged { size -> stackHeight.intValue = size.height }, ) } - HeadsUpNotificationSpace(viewModel = viewModel) + HeadsUpNotificationSpace(stackScrollView = stackScrollView, viewModel = viewModel) } } 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..7ca68fb40b61 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.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text 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, - horizontalArrangement = Arrangement.Start, + viewModel = overlayShadeViewModel, + horizontalArrangement = Arrangement.End, 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.fillMaxWidth(), + ) + } + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 54a98ddaa01a..8195df3b01e8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.ui.composable +import android.view.ViewGroup +import android.widget.FrameLayout import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth @@ -24,6 +26,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView @@ -164,11 +167,8 @@ private fun QuickSettingsContent( state: () -> QSSceneAdapter.State, modifier: Modifier = Modifier, ) { - val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle(null) - val isCustomizing by - qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle( - qsSceneAdapter.isCustomizerShowing.value - ) + val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle() + val isCustomizing by qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() QuickSettingsTheme { val context = LocalContext.current @@ -180,15 +180,34 @@ private fun QuickSettingsContent( qsView?.let { view -> Box( modifier = - modifier.fillMaxWidth().thenIf(isCustomizing) { Modifier.fillMaxHeight() } + modifier + .fillMaxWidth() + .thenIf(isCustomizing) { Modifier.fillMaxHeight() } + .drawWithContent { + qsSceneAdapter.applyLatestExpansionAndSquishiness() + drawContent() + } ) { AndroidView( modifier = Modifier.fillMaxWidth(), - factory = { _ -> + factory = { context -> + qsSceneAdapter.setState(state()) + FrameLayout(context).apply { + (view.parent as? ViewGroup)?.removeView(view) + addView(view) + } + }, + // When the view changes (e.g. due to a theme change), this will be recomposed + // if needed and the new view will be attached to the FrameLayout here. + update = { qsSceneAdapter.setState(state()) - view + if (view.parent != it) { + it.removeAllViews() + (view.parent as? ViewGroup)?.removeView(view) + it.addView(view) + } }, - update = { qsSceneAdapter.setState(state()) } + onRelease = { it.removeAllViews() } ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index d76b19f3fa82..0ee485c496be 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -42,7 +42,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState @@ -60,7 +59,6 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope @@ -79,14 +77,13 @@ import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.dagger.MediaModule -import com.android.systemui.notifications.ui.composable.NotificationScrollingStack +import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.res.R 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.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader import com.android.systemui.shade.ui.composable.Shade @@ -99,7 +96,6 @@ import com.android.systemui.statusbar.phone.ui.TintedIconManager import dagger.Lazy import javax.inject.Inject import javax.inject.Named -import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn @@ -368,15 +364,11 @@ private fun SceneScope.QuickSettingsScene( Modifier.align(Alignment.CenterHorizontally).sysuiResTag("qs_footer_actions"), ) } - NotificationScrollingStack( + HeadsUpNotificationSpace( stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, - shadeSession = shadeSession, - maxScrimTop = { screenHeight }, - shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, - shadeMode = ShadeMode.Single, - modifier = - Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }, + modifier = Modifier.align(Alignment.BottomCenter), + isPeekFromBottom = true, ) } } 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/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index 975829ab3760..efda4cd3638e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -17,17 +17,28 @@ package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.absoluteOffset import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.IntOffset import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneFloatAsState +import com.android.internal.policy.SystemBarUtils import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace import com.android.systemui.qs.ui.composable.QuickSettings +import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow @@ -39,6 +50,8 @@ import kotlinx.coroutines.flow.StateFlow class GoneScene @Inject constructor( + private val notificationStackScrolLView: Lazy<NotificationScrollView>, + private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, private val viewModel: GoneSceneViewModel, ) : ComposableScene { override val key = Scenes.Gone @@ -55,5 +68,28 @@ constructor( key = QuickSettings.SharedValues.TilesSquishiness, ) Spacer(modifier.fillMaxSize()) + HeadsUpNotificationStack( + stackScrollView = notificationStackScrolLView.get(), + viewModel = notificationsPlaceholderViewModel + ) } } + +@Composable +private fun SceneScope.HeadsUpNotificationStack( + stackScrollView: NotificationScrollView, + viewModel: NotificationsPlaceholderViewModel, +) { + val context = LocalContext.current + val density = LocalDensity.current + val statusBarHeight = SystemBarUtils.getStatusBarHeight(context) + val headsUpPadding = + with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() } + + HeadsUpNotificationSpace( + stackScrollView = stackScrollView, + viewModel = viewModel, + modifier = + Modifier.absoluteOffset { IntOffset(x = 0, y = statusBarHeight + headsUpPadding) } + ) +} 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..3255b0805fdb 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,16 +8,21 @@ 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 import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition +import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.composable.Shade /** @@ -37,6 +42,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 +59,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, @@ -90,4 +103,10 @@ val SceneContainerTransitions = transitions { y = { Shade.Dimensions.ScrimOverscrollLimit } ) } + overscroll(Scenes.NotificationsShade, Orientation.Vertical) { + translate(OverlayShade.Elements.Panel, y = { OverlayShade.Dimensions.OverscrollLimit }) + } + overscroll(Scenes.QuickSettingsShade, Orientation.Vertical) { + translate(OverlayShade.Elements.Panel, y = { OverlayShade.Dimensions.OverscrollLimit }) + } } 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..6b3b7602050d --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.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.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.Panel, 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..ec2f14f34dba --- /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.Panel, 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..a7302061d02f 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,16 @@ 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.element(OverlayShade.Elements.Panel).panelSize(), + content = content + ) } } } @@ -111,9 +124,24 @@ 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) + val Panel = ElementKey("OverlayShadePanel", scenePicker = LowestZIndexScenePicker) val PanelBackground = ElementKey("OverlayShadePanelBackground", scenePicker = LowestZIndexScenePicker) } @@ -127,6 +155,9 @@ object OverlayShade { object Dimensions { val ScrimContentPadding = 16.dp val PanelCornerRadius = 46.dp + val PanelWidthMedium = 390.dp + val PanelWidthLarge = 474.dp + val OverscrollLimit = 100f } object Shapes { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 9d689fc25b23..33a630c8086a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.clipScrollableContainer import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -42,6 +43,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -67,6 +69,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf +import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout @@ -235,6 +238,10 @@ private fun SceneScope.SingleShade( val shouldPunchHoleBehindScrim = layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) || layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) + // Media is visible and we are in landscape on a small height screen + val mediaInRow = + isMediaVisible && + LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact Box( modifier = @@ -274,22 +281,39 @@ private fun SceneScope.SingleShade( createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, ) - Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) { - QuickSettings( - viewModel.qsSceneAdapter, - { viewModel.qsSceneAdapter.qqsHeight }, - isSplitShade = false, - squishiness = { tileSquishiness }, + + val content: @Composable (Modifier) -> Unit = { modifier -> + Box( + Modifier.element(QuickSettings.Elements.QuickQuickSettings) + .then(modifier) + ) { + QuickSettings( + viewModel.qsSceneAdapter, + { viewModel.qsSceneAdapter.qqsHeight }, + isSplitShade = false, + squishiness = { tileSquishiness }, + ) + } + + MediaCarousel( + isVisible = isMediaVisible, + mediaHost = mediaHost, + modifier = Modifier.fillMaxWidth().then(modifier), + carouselController = mediaCarouselController, ) } - MediaCarousel( - isVisible = isMediaVisible, - mediaHost = mediaHost, - modifier = Modifier.fillMaxWidth(), - carouselController = mediaCarouselController, - ) - + if (!mediaInRow) { + content(Modifier) + } else { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + content(Modifier.weight(1f)) + } + } Spacer(modifier = Modifier.height(16.dp)) } }, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/EdgeToEdgeDialogDelegate.kt index 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/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 271eb9601dbd..fbf91b702fb9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -68,9 +68,7 @@ fun VolumeSlider( state.a11yClickDescription?.let { customActions = listOf( - CustomAccessibilityAction( - it, - ) { + CustomAccessibilityAction(it) { onIconTapped() true } 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/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt new file mode 100644 index 000000000000..7be34cabfaf8 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.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.compose.animation.scene + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.positionChange +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import kotlin.math.abs + +private const val TRAVEL_RATIO_THRESHOLD = .5f + +/** + * {@link CommunalSwipeDetector} provides an implementation of {@link SwipeDetector} and {@link + * SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable + * hub. + */ +class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) : + SwipeSourceDetector, SwipeDetector { + override fun source( + layoutSize: IntSize, + position: IntOffset, + density: Density, + orientation: Orientation + ): SwipeSource? { + return lastDirection + } + + override fun detectSwipe(change: PointerInputChange): Boolean { + if (change.positionChange().x > 0) { + lastDirection = Edge.Left + } else { + lastDirection = Edge.Right + } + + // Determine whether the ratio of the distance traveled horizontally to the distance + // traveled vertically exceeds the threshold. + return abs(change.positionChange().x / change.positionChange().y) > TRAVEL_RATIO_THRESHOLD + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index d2a1de3e4695..f0fb9f62fdad 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -371,96 +371,57 @@ private fun prepareInterruption( transition: TransitionState.Transition, previousTransition: TransitionState.Transition, ) { - val previousUniqueState = reconcileStates(element, previousTransition) - if (previousUniqueState == null) { - reconcileStates(element, transition) - return - } - - val fromSceneState = element.sceneStates[transition.fromScene] - val toSceneState = element.sceneStates[transition.toScene] - - if ( - fromSceneState == null || - toSceneState == null || - sharedElementTransformation(element.key, transition)?.enabled != false - ) { - // If there is only one copy of the element or if the element is shared, animate deltas in - // both scenes. - fromSceneState?.updateValuesBeforeInterruption(previousUniqueState) - toSceneState?.updateValuesBeforeInterruption(previousUniqueState) - } + val sceneStates = element.sceneStates + sceneStates[previousTransition.fromScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[previousTransition.toScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[transition.fromScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[transition.toScene]?.selfUpdateValuesBeforeInterruption() + + reconcileStates(element, previousTransition) + reconcileStates(element, transition) } /** * Reconcile the state of [element] in the fromScene and toScene of [transition] so that the values * before interruption have their expected values, taking shared transitions into account. - * - * If the element had a unique state, i.e. it is shared in [transition] or it is only present in one - * of the scenes, return it. */ private fun reconcileStates( element: Element, transition: TransitionState.Transition, -): Element.SceneState? { - val fromSceneState = element.sceneStates[transition.fromScene] - val toSceneState = element.sceneStates[transition.toScene] - when { - // Element is in both scenes. - fromSceneState != null && toSceneState != null -> { - val isSharedTransformationDisabled = - sharedElementTransformation(element.key, transition)?.enabled == false - when { - // Element shared transition is disabled so the element is placed in both scenes. - isSharedTransformationDisabled -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - toSceneState.updateValuesBeforeInterruption(toSceneState) - return null - } - - // Element is shared and placed in fromScene only. - fromSceneState.lastOffset != Offset.Unspecified -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - toSceneState.updateValuesBeforeInterruption(fromSceneState) - return fromSceneState - } - - // Element is shared and placed in toScene only. - toSceneState.lastOffset != Offset.Unspecified -> { - fromSceneState.updateValuesBeforeInterruption(toSceneState) - toSceneState.updateValuesBeforeInterruption(toSceneState) - return toSceneState - } - - // Element is in none of the scenes. - else -> { - fromSceneState.updateValuesBeforeInterruption(null) - toSceneState.updateValuesBeforeInterruption(null) - return null - } - } - } - - // Element is only in fromScene. - fromSceneState != null -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - return fromSceneState - } +) { + val fromSceneState = element.sceneStates[transition.fromScene] ?: return + val toSceneState = element.sceneStates[transition.toScene] ?: return + if (!isSharedElementEnabled(element.key, transition)) { + return + } - // Element is only in toScene. - toSceneState != null -> { - toSceneState.updateValuesBeforeInterruption(toSceneState) - return toSceneState - } - else -> return null + if ( + fromSceneState.offsetBeforeInterruption != Offset.Unspecified && + toSceneState.offsetBeforeInterruption == Offset.Unspecified + ) { + // Element is shared and placed in fromScene only. + toSceneState.updateValuesBeforeInterruption(fromSceneState) + } else if ( + toSceneState.offsetBeforeInterruption != Offset.Unspecified && + fromSceneState.offsetBeforeInterruption == Offset.Unspecified + ) { + // Element is shared and placed in toScene only. + fromSceneState.updateValuesBeforeInterruption(toSceneState) } } -private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState?) { - offsetBeforeInterruption = lastState?.lastOffset ?: Offset.Unspecified - sizeBeforeInterruption = lastState?.lastSize ?: Element.SizeUnspecified - scaleBeforeInterruption = lastState?.lastScale ?: Scale.Unspecified - alphaBeforeInterruption = lastState?.lastAlpha ?: Element.AlphaUnspecified +private fun Element.SceneState.selfUpdateValuesBeforeInterruption() { + offsetBeforeInterruption = lastOffset + sizeBeforeInterruption = lastSize + scaleBeforeInterruption = lastScale + alphaBeforeInterruption = lastAlpha +} + +private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState) { + offsetBeforeInterruption = lastState.offsetBeforeInterruption + sizeBeforeInterruption = lastState.sizeBeforeInterruption + scaleBeforeInterruption = lastState.scaleBeforeInterruption + alphaBeforeInterruption = lastState.alphaBeforeInterruption clearInterruptionDeltas() } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 0fc0053ce4a1..3cc8431cd87e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -72,6 +72,7 @@ internal fun Modifier.multiPointerDraggable( enabled: () -> Boolean, startDragImmediately: (startedPosition: Offset) -> Boolean, onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + swipeDetector: SwipeDetector = DefaultSwipeDetector, ): Modifier = this.then( MultiPointerDraggableElement( @@ -79,6 +80,7 @@ internal fun Modifier.multiPointerDraggable( enabled, startDragImmediately, onDragStarted, + swipeDetector, ) ) @@ -88,6 +90,7 @@ private data class MultiPointerDraggableElement( private val startDragImmediately: (startedPosition: Offset) -> Boolean, private val onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + private val swipeDetector: SwipeDetector, ) : ModifierNodeElement<MultiPointerDraggableNode>() { override fun create(): MultiPointerDraggableNode = MultiPointerDraggableNode( @@ -95,6 +98,7 @@ private data class MultiPointerDraggableElement( enabled = enabled, startDragImmediately = startDragImmediately, onDragStarted = onDragStarted, + swipeDetector = swipeDetector, ) override fun update(node: MultiPointerDraggableNode) { @@ -102,6 +106,7 @@ private data class MultiPointerDraggableElement( node.enabled = enabled node.startDragImmediately = startDragImmediately node.onDragStarted = onDragStarted + node.swipeDetector = swipeDetector } } @@ -111,6 +116,7 @@ internal class MultiPointerDraggableNode( var startDragImmediately: (startedPosition: Offset) -> Boolean, var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + var swipeDetector: SwipeDetector = DefaultSwipeDetector, ) : PointerInputModifierNode, DelegatingNode(), @@ -199,6 +205,7 @@ internal class MultiPointerDraggableNode( onDragCancel = { controller -> controller.onStop(velocity = 0f, canChangeScene = true) }, + swipeDetector = swipeDetector ) } catch (exception: CancellationException) { // If the coroutine scope is active, we can just restart the drag cycle. @@ -226,7 +233,8 @@ internal class MultiPointerDraggableNode( (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit, onDragEnd: (controller: DragController) -> Unit, - onDragCancel: (controller: DragController) -> Unit + onDragCancel: (controller: DragController) -> Unit, + swipeDetector: SwipeDetector, ) { // Wait for a consumable event in [PointerEventPass.Main] pass val consumablePointer = awaitConsumableEvent().changes.first() @@ -238,8 +246,10 @@ internal class MultiPointerDraggableNode( consumablePointer } else { val onSlopReached = { change: PointerInputChange, over: Float -> - change.consume() - overSlop = over + if (swipeDetector.detectSwipe(change)) { + change.consume() + overSlop = over + } } // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 11e711ace971..cf8c5841f797 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -55,6 +55,7 @@ fun SceneTransitionLayout( state: SceneTransitionLayoutState, modifier: Modifier = Modifier, swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, + swipeDetector: SwipeDetector = DefaultSwipeDetector, @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f, scenes: SceneTransitionLayoutScope.() -> Unit, ) { @@ -62,6 +63,7 @@ fun SceneTransitionLayout( state, modifier, swipeSourceDetector, + swipeDetector, transitionInterceptionThreshold, onLayoutImpl = null, scenes, @@ -95,6 +97,7 @@ fun SceneTransitionLayout( transitions: SceneTransitions, modifier: Modifier = Modifier, swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, + swipeDetector: SwipeDetector = DefaultSwipeDetector, @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f, enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, scenes: SceneTransitionLayoutScope.() -> Unit, @@ -111,6 +114,7 @@ fun SceneTransitionLayout( state, modifier, swipeSourceDetector, + swipeDetector, transitionInterceptionThreshold, scenes, ) @@ -467,6 +471,7 @@ internal fun SceneTransitionLayoutForTesting( state: SceneTransitionLayoutState, modifier: Modifier = Modifier, swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, + swipeDetector: SwipeDetector = DefaultSwipeDetector, transitionInterceptionThreshold: Float = 0f, onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null, scenes: SceneTransitionLayoutScope.() -> Unit, @@ -502,5 +507,5 @@ internal fun SceneTransitionLayoutForTesting( layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold } - layoutImpl.Content(modifier) + layoutImpl.Content(modifier, swipeDetector) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 7856498aa365..c614265e2ae1 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -185,14 +185,14 @@ internal class SceneTransitionLayoutImpl( } @Composable - internal fun Content(modifier: Modifier) { + internal fun Content(modifier: Modifier, swipeDetector: SwipeDetector) { Box( modifier // Handle horizontal and vertical swipes on this layout. // Note: order here is important and will give a slight priority to the vertical // swipes. - .swipeToScene(horizontalDraggableHandler) - .swipeToScene(verticalDraggableHandler) + .swipeToScene(horizontalDraggableHandler, swipeDetector) + .swipeToScene(verticalDraggableHandler, swipeDetector) .then(LayoutElement(layoutImpl = this)) ) { LookaheadScope { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt new file mode 100644 index 000000000000..54ee78366875 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt @@ -0,0 +1,40 @@ +/* + * 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.compose.animation.scene + +import androidx.compose.runtime.Stable +import androidx.compose.ui.input.pointer.PointerInputChange + +/** {@link SwipeDetector} helps determine whether a swipe gestured has occurred. */ +@Stable +interface SwipeDetector { + /** + * Invoked on changes to pointer input. Returns {@code true} if a swipe has been recognized, + * {@code false} otherwise. + */ + fun detectSwipe(change: PointerInputChange): Boolean +} + +val DefaultSwipeDetector = PassthroughSwipeDetector() + +/** An {@link SwipeDetector} implementation that recognizes a swipe on any input. */ +class PassthroughSwipeDetector : SwipeDetector { + override fun detectSwipe(change: PointerInputChange): Boolean { + // Simply accept all changes as a swipe + return true + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index b618369c2369..171e2430c004 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -31,14 +31,18 @@ import androidx.compose.ui.unit.IntSize * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state. */ @Stable -internal fun Modifier.swipeToScene(draggableHandler: DraggableHandlerImpl): Modifier { - return this.then(SwipeToSceneElement(draggableHandler)) +internal fun Modifier.swipeToScene( + draggableHandler: DraggableHandlerImpl, + swipeDetector: SwipeDetector +): Modifier { + return this.then(SwipeToSceneElement(draggableHandler, swipeDetector)) } private data class SwipeToSceneElement( val draggableHandler: DraggableHandlerImpl, + val swipeDetector: SwipeDetector ) : ModifierNodeElement<SwipeToSceneNode>() { - override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler) + override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler, swipeDetector) override fun update(node: SwipeToSceneNode) { node.draggableHandler = draggableHandler @@ -47,6 +51,7 @@ private data class SwipeToSceneElement( private class SwipeToSceneNode( draggableHandler: DraggableHandlerImpl, + swipeDetector: SwipeDetector, ) : DelegatingNode(), PointerInputModifierNode { private val delegate = delegate( @@ -55,6 +60,7 @@ private class SwipeToSceneNode( enabled = ::enabled, startDragImmediately = ::startDragImmediately, onDragStarted = draggableHandler::onDragStarted, + swipeDetector = swipeDetector, ) ) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index aa6d1130fc2a..4bb643f8b89e 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalViewConfiguration @@ -346,4 +347,69 @@ class MultiPointerDraggableTest { continueDraggingDown() assertThat(stopped).isTrue() } + + @Test + fun multiPointerSwipeDetectorInteraction() { + val size = 200f + val middle = Offset(size / 2f, size / 2f) + + var started = false + + var capturedChange: PointerInputChange? = null + var swipeConsume = false + + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + Box( + Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() }) + .multiPointerDraggable( + orientation = Orientation.Vertical, + enabled = { true }, + startDragImmediately = { false }, + swipeDetector = + object : SwipeDetector { + override fun detectSwipe(change: PointerInputChange): Boolean { + capturedChange = change + return swipeConsume + } + }, + onDragStarted = { _, _, _ -> + started = true + object : DragController { + override fun onDrag(delta: Float) {} + + override fun onStop(velocity: Float, canChangeScene: Boolean) {} + } + }, + ) + ) {} + } + + fun startDraggingDown() { + rule.onRoot().performTouchInput { + down(middle) + moveBy(Offset(0f, touchSlop)) + } + } + + fun continueDraggingDown() { + rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) } + } + + startDraggingDown() + assertThat(capturedChange).isNotNull() + capturedChange = null + assertThat(started).isFalse() + + swipeConsume = true + continueDraggingDown() + assertThat(capturedChange).isNotNull() + capturedChange = null + + continueDraggingDown() + assertThat(capturedChange).isNull() + + assertThat(started).isTrue() + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index bdeab797d165..079c1d922275 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -17,7 +17,6 @@ package com.android.systemui.shared.clocks import android.animation.TimeInterpolator import android.annotation.ColorInt -import android.annotation.FloatRange import android.annotation.IntRange import android.annotation.SuppressLint import android.content.Context @@ -27,7 +26,7 @@ import android.text.TextUtils import android.text.format.DateFormat import android.util.AttributeSet import android.util.MathUtils.constrainedMap -import android.util.TypedValue +import android.util.TypedValue.COMPLEX_UNIT_PX import android.view.View import android.view.View.MeasureSpec.EXACTLY import android.widget.TextView @@ -219,9 +218,7 @@ constructor( override fun setTextSize(type: Int, size: Float) { super.setTextSize(type, size) - if (type == TypedValue.COMPLEX_UNIT_PX) { - lastUnconstrainedTextSize = size - } + lastUnconstrainedTextSize = if (type == COMPLEX_UNIT_PX) size else Float.MAX_VALUE } @SuppressLint("DrawAllocation") @@ -234,23 +231,19 @@ constructor( MeasureSpec.getMode(heightMeasureSpec) == EXACTLY ) { // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize - super.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) - ) + val size = min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) + super.setTextSize(COMPLEX_UNIT_PX, size) } super.onMeasure(widthMeasureSpec, heightMeasureSpec) - val animator = textAnimator - if (animator == null) { - textAnimator = - textAnimatorFactory(layout, ::invalidate)?.also { - onTextAnimatorInitialized?.invoke(it) - onTextAnimatorInitialized = null - } - } else { - animator.updateLayout(layout) - } + textAnimator?.let { animator -> animator.updateLayout(layout, textSize) } + ?: run { + textAnimator = + textAnimatorFactory(layout, ::invalidate).also { + onTextAnimatorInitialized?.invoke(it) + onTextAnimatorInitialized = null + } + } if (migratedClocks && hasCustomPositionUpdatedAnimation) { // Expand width to avoid clock being clipped during stepping animation @@ -307,18 +300,18 @@ constructor( logger.d("animateColorChange") setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = null, /* using current color */ animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = lockScreenColor, animate = true, + interpolator = null, duration = COLOR_ANIM_DURATION, delay = 0, onAnimationEnd = null @@ -329,16 +322,15 @@ constructor( logger.d("animateAppearOnLockscreen") setTextStyle( weight = dozingWeight, - textSize = -1f, color = lockScreenColor, animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = lockScreenColor, animate = true, duration = APPEAR_ANIM_DURATION, @@ -356,16 +348,15 @@ constructor( logger.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, - textSize = -1f, color = lockScreenColor, animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = dozingWeightInternal, - textSize = -1f, color = dozingColor, animate = animate, interpolator = Interpolators.EMPHASIZED_DECELERATE, @@ -385,9 +376,9 @@ constructor( val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, - textSize = -1f, color = null, animate = true, + interpolator = null, duration = CHARGE_ANIM_DURATION_PHASE_1, delay = 0, onAnimationEnd = null @@ -395,9 +386,9 @@ constructor( } setTextStyle( weight = if (isDozing()) lockScreenWeight else dozingWeight, - textSize = -1f, color = null, animate = true, + interpolator = null, duration = CHARGE_ANIM_DURATION_PHASE_0, delay = chargeAnimationDelay.toLong(), onAnimationEnd = startAnimPhase2 @@ -408,9 +399,9 @@ constructor( logger.d("animateDoze") setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, - textSize = -1f, color = if (isDozing) dozingColor else lockScreenColor, animate = animate, + interpolator = null, duration = DOZE_ANIM_DURATION, delay = 0, onAnimationEnd = null @@ -448,7 +439,6 @@ constructor( */ private fun setTextStyle( @IntRange(from = 0, to = 1000) weight: Int, - @FloatRange(from = 0.0) textSize: Float, color: Int?, animate: Boolean, interpolator: TimeInterpolator?, @@ -459,7 +449,6 @@ constructor( textAnimator?.let { it.setTextStyle( weight = weight, - textSize = textSize, color = color, animate = animate && isAnimationEnabled, duration = duration, @@ -474,7 +463,6 @@ constructor( onTextAnimatorInitialized = { textAnimator -> textAnimator.setTextStyle( weight = weight, - textSize = textSize, color = color, animate = false, duration = duration, @@ -487,27 +475,6 @@ constructor( } } - private fun setTextStyle( - @IntRange(from = 0, to = 1000) weight: Int, - @FloatRange(from = 0.0) textSize: Float, - color: Int?, - animate: Boolean, - duration: Long, - delay: Long, - onAnimationEnd: Runnable? - ) { - setTextStyle( - weight = weight, - textSize = textSize, - color = color, - animate = animate, - interpolator = null, - duration = duration, - delay = delay, - onAnimationEnd = onAnimationEnd - ) - } - fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context)) fun refreshFormat(use24HourFormat: Boolean) { Patterns.update(context) 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/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 256687b56f4e..89bafb952211 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -16,6 +16,12 @@ package com.android.systemui.bouncer.ui.viewmodel +import android.view.KeyEvent.KEYCODE_0 +import android.view.KeyEvent.KEYCODE_4 +import android.view.KeyEvent.KEYCODE_A +import android.view.KeyEvent.KEYCODE_DEL +import android.view.KeyEvent.KEYCODE_NUMPAD_0 +import androidx.compose.ui.input.key.KeyEventType import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey @@ -34,6 +40,8 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlin.random.Random +import kotlin.random.nextInt import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -444,6 +452,44 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(pin).hasSize(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1) } + @Test + fun onKeyboardInput_pinInput_isUpdated() = + testScope.runTest { + val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) + lockDeviceAndOpenPinBouncer() + val random = Random(System.currentTimeMillis()) + // Generate a random 4 digit PIN + val expectedPin = + with(random) { arrayOf(nextInt(0..9), nextInt(0..9), nextInt(0..9), nextInt(0..9)) } + + // Enter the PIN using NUM pad and normal number keyboard events + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_0 + expectedPin[0]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_0 + expectedPin[0]) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_NUMPAD_0 + expectedPin[1]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_NUMPAD_0 + expectedPin[1]) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_0 + expectedPin[2]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_0 + expectedPin[2]) + + // Enter an additional digit in between and delete it + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_4) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_4) + + // Delete that additional digit + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_DEL) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_DEL) + + // Try entering a non digit character, this should be ignored. + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_A) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_A) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_NUMPAD_0 + expectedPin[3]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_NUMPAD_0 + expectedPin[3]) + + assertThat(pin).containsExactly(*expectedPin) + } + private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt index e39ad4f0b405..a676c7db4290 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt @@ -25,15 +25,18 @@ import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.brightness.data.model.LinearBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -74,6 +77,8 @@ class ScreenBrightnessDisplayManagerRepositoryTest : SysuiTestCase() { ScreenBrightnessDisplayManagerRepository( displayId, displayManager, + FakeLogBuffer.Factory.create(), + mock<TableLogBuffer>(), kosmos.applicationCoroutineScope, kosmos.testDispatcher, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt index 33c44f8a331e..b6616bf0c8de 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt @@ -20,13 +20,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.display.BrightnessUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.brightness.data.model.LinearBrightness import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository import com.android.systemui.brightness.data.repository.screenBrightnessRepository -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.GammaBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -41,7 +44,14 @@ class ScreenBrightnessInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() - private val underTest = ScreenBrightnessInteractor(kosmos.screenBrightnessRepository) + private val underTest = + with(kosmos) { + ScreenBrightnessInteractor( + screenBrightnessRepository, + applicationCoroutineScope, + mock<TableLogBuffer>() + ) + } @Test fun gammaBrightness() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt index 0058ee4a9c4e..8402676dbd6b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt @@ -20,15 +20,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.display.BrightnessUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.brightness.data.model.LinearBrightness import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.GammaBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.testKosmos @@ -52,6 +53,7 @@ class BrightnessSliderViewModelTest : SysuiTestCase() { BrightnessSliderViewModel( screenBrightnessInteractor, brightnessPolicyEnforcementInteractor, + applicationCoroutineScope, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 0ab09595d6b0..e61b2d0f2e74 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue @@ -35,6 +36,8 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notificationShadeWindowController +import com.android.systemui.statusbar.phone.centralSurfaces +import com.android.systemui.statusbar.phone.centralSurfacesOptional import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat @@ -49,6 +52,7 @@ import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -67,6 +71,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { CommunalSceneStartable( dockManager = dockManager, communalInteractor = communalInteractor, + communalSceneInteractor = communalSceneInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, keyguardInteractor = keyguardInteractor, systemSettings = fakeSettings, @@ -74,6 +79,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { applicationScope = applicationCoroutineScope, bgScope = applicationCoroutineScope, mainDispatcher = testDispatcher, + centralSurfacesOpt = centralSurfacesOptional, ) .apply { start() } @@ -90,9 +96,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun keyguardGoesAway_forceBlankScene() = with(kosmos) { testScope.runTest { - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) - communalInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -110,7 +116,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun deviceDocked_forceCommunalScene() = with(kosmos) { testScope.runTest { - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Blank) updateDocked(true) @@ -127,8 +133,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun occluded_forceBlankScene() = with(kosmos) { testScope.runTest { - val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.changeScene(CommunalScenes.Communal) + whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(false) + val scene by collectLastValue(communalSceneInteractor.currentScene) + communalSceneInteractor.changeScene(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) updateDocked(true) @@ -142,10 +149,29 @@ class CommunalSceneStartableTest : SysuiTestCase() { } @Test + fun occluded_doesNotForceBlankSceneIfLaunchingActivityOverLockscreen() = + with(kosmos) { + testScope.runTest { + whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(true) + val scene by collectLastValue(communalSceneInteractor.currentScene) + communalSceneInteractor.changeScene(CommunalScenes.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + updateDocked(true) + fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + testScope = this + ) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + } + } + + @Test fun deviceDocked_doesNotForceCommunalIfTransitioningFromCommunal() = with(kosmos) { testScope.runTest { - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Blank) updateDocked(true) @@ -162,8 +188,8 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun deviceAsleep_forceBlankSceneAfterTimeout() = with(kosmos) { testScope.runTest { - val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.changeScene(CommunalScenes.Communal) + val scene by collectLastValue(communalSceneInteractor.currentScene) + communalSceneInteractor.changeScene(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -183,8 +209,8 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun deviceAsleep_wakesUpBeforeTimeout_noChangeInScene() = with(kosmos) { testScope.runTest { - val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.changeScene(CommunalScenes.Communal) + val scene by collectLastValue(communalSceneInteractor.currentScene) + communalSceneInteractor.changeScene(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -212,8 +238,8 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun dockingOnLockscreen_forcesCommunal() = with(kosmos) { testScope.runTest { - communalInteractor.changeScene(CommunalScenes.Blank) - val scene by collectLastValue(communalInteractor.desiredScene) + communalSceneInteractor.changeScene(CommunalScenes.Blank) + val scene by collectLastValue(communalSceneInteractor.currentScene) // device is docked while on the lockscreen fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -234,8 +260,8 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() = with(kosmos) { testScope.runTest { - communalInteractor.changeScene(CommunalScenes.Blank) - val scene by collectLastValue(communalInteractor.desiredScene) + communalSceneInteractor.changeScene(CommunalScenes.Blank) + val scene by collectLastValue(communalSceneInteractor.currentScene) // device is docked while on the lockscreen fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -266,9 +292,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal) - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Scene times out back to blank after the screen timeout. @@ -283,11 +309,11 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is not dreaming and on communal. updateDreaming(false) - communalInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal) // Scene stays as Communal advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) } } @@ -298,9 +324,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal) - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Wait a bit, but not long enough to timeout. @@ -321,9 +347,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is on communal, but not dreaming. updateDreaming(false) - communalInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal) - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Wait a bit, but not long enough to timeout, then start dreaming. @@ -342,11 +368,11 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is on communal. - communalInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal) // Device stays on the hub after the timeout since we're not dreaming. advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Start dreaming. @@ -363,9 +389,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. updateDreaming(true) - communalInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal) - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Wait a bit, but not long enough to timeout. @@ -392,9 +418,9 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Device is dreaming and on communal. updateDreaming(true) - communalInteractor.changeScene(CommunalScenes.Communal) + communalSceneInteractor.changeScene(CommunalScenes.Communal) - val scene by collectLastValue(communalInteractor.desiredScene) + val scene by collectLastValue(communalSceneInteractor.currentScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Scene times out back to blank after the screen timeout. @@ -421,12 +447,6 @@ class CommunalSceneStartableTest : SysuiTestCase() { runCurrent() } - private suspend fun TestScope.enableCommunal() = - with(kosmos) { - setCommunalAvailable(true) - runCurrent() - } - companion object { private const val SCREEN_TIMEOUT = 1000 } 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..fd0bf4dae198 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,8 @@ class CommunalRepositoryImplTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val underTest by lazy { - CommunalRepositoryImpl( + CommunalSceneRepositoryImpl( + kosmos.applicationCoroutineScope, 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..cebcbc98bbaa 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,9 +21,9 @@ 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.fakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -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 @@ -56,7 +56,7 @@ class CommunalInteractorCommunalDisabledTest : SysuiTestCase() { @Before fun setUp() { - communalRepository = kosmos.fakeCommunalRepository + communalRepository = kosmos.fakeCommunalSceneRepository widgetRepository = kosmos.fakeCommunalWidgetRepository keyguardRepository = kosmos.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..ffa63d83a97f 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,15 +39,16 @@ 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 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.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 @@ -131,7 +132,7 @@ class CommunalInteractorTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) tutorialRepository = kosmos.fakeCommunalTutorialRepository - communalRepository = kosmos.fakeCommunalRepository + communalRepository = kosmos.fakeCommunalSceneRepository mediaRepository = kosmos.fakeCommunalMediaRepository widgetRepository = kosmos.fakeCommunalWidgetRepository smartspaceRepository = kosmos.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/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt new file mode 100644 index 000000000000..aad2e6001f1c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt @@ -0,0 +1,227 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.SysuiTestCase +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.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalSceneInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val repository = kosmos.communalSceneRepository + private val underTest by lazy { kosmos.communalSceneInteractor } + + @Test + fun changeScene() = + testScope.runTest { + val currentScene by collectLastValue(underTest.currentScene) + assertThat(currentScene).isEqualTo(CommunalScenes.Blank) + + underTest.changeScene(CommunalScenes.Communal) + assertThat(currentScene).isEqualTo(CommunalScenes.Communal) + } + + @Test + fun snapToScene() = + testScope.runTest { + val currentScene by collectLastValue(underTest.currentScene) + assertThat(currentScene).isEqualTo(CommunalScenes.Blank) + + underTest.snapToScene(CommunalScenes.Communal) + assertThat(currentScene).isEqualTo(CommunalScenes.Communal) + } + + @Test + fun transitionProgress_fullProgress() = + testScope.runTest { + val transitionProgress by + collectLastValue(underTest.transitionProgressToScene(CommunalScenes.Blank)) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Blank)) + + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(CommunalScenes.Communal) + ) + underTest.setTransitionState(transitionState) + + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) + } + + @Test + fun transitionProgress_transitioningAwayFromTrackedScene() = + testScope.runTest { + val transitionProgress by + collectLastValue(underTest.transitionProgressToScene(CommunalScenes.Blank)) + + val progress = MutableStateFlow(0f) + underTest.setTransitionState( + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Communal), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + ) + + // Partially transition. + progress.value = .4f + + // This is a transition we don't care about the progress of. + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.OtherTransition) + + // Transition is at full progress. + progress.value = 1f + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.OtherTransition) + + // Transition finishes. + underTest.setTransitionState( + MutableStateFlow(ObservableTransitionState.Idle(CommunalScenes.Communal)) + ) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) + } + + @Test + fun transitionProgress_transitioningToTrackedScene() = + testScope.runTest { + val transitionProgress by + collectLastValue(underTest.transitionProgressToScene(CommunalScenes.Communal)) + + val progress = MutableStateFlow(0f) + underTest.setTransitionState( + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Communal), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + ) + + // Partially transition. + progress.value = .4f + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Transition(0.4f)) + + // Transition is at full progress. + progress.value = 1f + assertThat(transitionProgress).isEqualTo(CommunalTransitionProgressModel.Transition(1f)) + + // Transition finishes. + underTest.setTransitionState( + MutableStateFlow(ObservableTransitionState.Idle(CommunalScenes.Communal)) + ) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) + } + + @Test + fun isIdleOnCommunal() = + testScope.runTest { + // isIdleOnCommunal is false when not on communal. + val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal) + assertThat(isIdleOnCommunal).isEqualTo(false) + + val transitionState: MutableStateFlow<ObservableTransitionState> = + MutableStateFlow(ObservableTransitionState.Idle(CommunalScenes.Communal)) + + // Transition to communal. + repository.setTransitionState(transitionState) + assertThat(isIdleOnCommunal).isEqualTo(true) + + // Start transition away from communal. + transitionState.value = + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Communal, + toScene = CommunalScenes.Blank, + currentScene = flowOf(CommunalScenes.Blank), + progress = flowOf(0f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + assertThat(isIdleOnCommunal).isEqualTo(false) + } + + @Test + fun isCommunalVisible() = + testScope.runTest { + // isCommunalVisible is false when not on communal. + val isCommunalVisible by collectLastValue(underTest.isCommunalVisible) + assertThat(isCommunalVisible).isEqualTo(false) + + val transitionState: MutableStateFlow<ObservableTransitionState> = + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Communal), + progress = flowOf(0f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + + // Start transition to communal. + repository.setTransitionState(transitionState) + assertThat(isCommunalVisible).isEqualTo(true) + + // Finish transition to communal + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) + assertThat(isCommunalVisible).isEqualTo(true) + + // Start transition away from communal. + transitionState.value = + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Communal, + toScene = CommunalScenes.Blank, + currentScene = flowOf(CommunalScenes.Blank), + progress = flowOf(1.0f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + assertThat(isCommunalVisible).isEqualTo(true) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/compose/CommunalHubUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/compose/CommunalHubUtilsTest.kt new file mode 100644 index 000000000000..643063e738da --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/compose/CommunalHubUtilsTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.compose + +import android.testing.TestableLooper +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class CommunalHubUtilsTest : SysuiTestCase() { + @Test + fun isPointerWithinEnabledRemoveButton_ensureDisabledStatePriority() { + assertThat( + isPointerWithinEnabledRemoveButton(false, mock<Offset>(), mock<LayoutCoordinates>()) + ) + .isFalse() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 6ca04dfca6a4..84dbfd488fe7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.log.CommunalUiEvent @@ -106,6 +107,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { underTest = CommunalEditModeViewModel( + kosmos.communalSceneInteractor, kosmos.communalInteractor, kosmos.communalSettingsInteractor, mediaHost, 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..5e19a41f345c 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,14 +28,15 @@ 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 -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.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalScenes @@ -106,7 +107,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 @@ -126,7 +127,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { mediaRepository = kosmos.fakeCommunalMediaRepository userRepository = kosmos.fakeUserRepository shadeTestUtil = kosmos.shadeTestUtil - communalRepository = kosmos.fakeCommunalRepository + communalRepository = kosmos.fakeCommunalSceneRepository kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) @@ -143,6 +144,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { context.resources, kosmos.keyguardTransitionInteractor, kosmos.keyguardInteractor, + kosmos.communalSceneInteractor, kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt index b4f87c47a0b0..420b11c4bde3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt @@ -72,6 +72,7 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { verify(activityStarter) .startPendingIntentMaybeDismissingKeyguard( eq(testIntent), + eq(false), isNull(), notNull(), refEq(fillInIntent), @@ -91,6 +92,7 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { verify(activityStarter) .startPendingIntentMaybeDismissingKeyguard( eq(testIntent), + eq(false), isNull(), isNull(), refEq(fillInIntent), 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..ee8a22c17455 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -45,8 +45,8 @@ 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.fakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository +import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable @@ -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 @@ -167,7 +166,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner) bouncerRepository = kosmos.fakeKeyguardBouncerRepository - communalRepository = kosmos.fakeCommunalRepository + communalRepository = kosmos.fakeCommunalSceneRepository whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController()) .thenReturn(mDreamOverlayContainerViewController) @@ -398,6 +397,9 @@ class DreamOverlayServiceTest : SysuiTestCase() { verify(mStateController).setOverlayActive(false) verify(mStateController).setLowLightActive(false) verify(mStateController).setEntryAnimationsFinished(false) + + // Verify touch monitor destroyed + verify(mTouchMonitor).destroy() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt index 723f6a2bfff4..9300db9a24c8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt @@ -16,29 +16,40 @@ package com.android.systemui.dreams.homecontrols import android.app.Activity +import android.content.Intent +import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL +import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_DREAM +import android.service.controls.ControlsProviderService.EXTRA_CONTROLS_SURFACE +import android.window.TaskFragmentInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.controls.settings.FakeControlsSettingsRepository import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope -import com.android.systemui.log.core.FakeLogBuffer.Factory.Companion.create import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.wakelock.WakeLockFake import com.google.common.truth.Truth.assertThat import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - +import org.mockito.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class HomeControlsDreamServiceTest : SysuiTestCase() { @@ -46,31 +57,38 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder - private lateinit var fakeWakeLock: WakeLockFake - - @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory - @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent - @Mock private lateinit var activity: Activity + private val fakeWakeLock = WakeLockFake() + private val fakeWakeLockBuilder by lazy { + WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) } + } + + private val taskFragmentComponent = mock<TaskFragmentComponent>() + private val activity = mock<Activity>() + private val onCreateCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>() + private val onInfoChangedCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>() + private val hideCallback = argumentCaptor<() -> Unit>() + private val dreamServiceDelegate = + mock<DreamServiceDelegate> { on { getActivity(any()) } doReturn activity } + + private val taskFragmentComponentFactory = + mock<TaskFragmentComponent.Factory> { + on { + create( + activity = eq(activity), + onCreateCallback = onCreateCallback.capture(), + onInfoChangedCallback = onInfoChangedCallback.capture(), + hide = hideCallback.capture(), + ) + } doReturn taskFragmentComponent + } - private lateinit var underTest: HomeControlsDreamService + private val underTest: HomeControlsDreamService by lazy { buildService() } @Before - fun setup() = - with(kosmos) { - MockitoAnnotations.initMocks(this@HomeControlsDreamServiceTest) - whenever(taskFragmentComponentFactory.create(any(), any(), any(), any())) - .thenReturn(taskFragmentComponent) - - fakeWakeLock = WakeLockFake() - fakeWakeLockBuilder = WakeLockFake.Builder(context) - fakeWakeLockBuilder.setWakeLock(fakeWakeLock) - - whenever(controlsComponent.getControlsListingController()) - .thenReturn(Optional.of(controlsListingController)) - - underTest = buildService { activity } - } + fun setup() { + whenever(kosmos.controlsComponent.getControlsListingController()) + .thenReturn(Optional.of(kosmos.controlsListingController)) + } @Test fun testOnAttachedToWindowCreatesTaskFragmentComponent() = @@ -90,9 +108,12 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { @Test fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() = testScope.runTest { - underTest = buildService { null } + val serviceWithNullActivity = + buildService( + mock<DreamServiceDelegate> { on { getActivity(underTest) } doReturn null } + ) - underTest.onAttachedToWindow() + serviceWithNullActivity.onAttachedToWindow() verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any()) } @@ -102,6 +123,7 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { underTest.onAttachedToWindow() assertThat(fakeWakeLock.isHeld).isTrue() } + @Test fun testDetachWindow_wakeLockCanBeReleased() = testScope.runTest { @@ -112,14 +134,60 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { assertThat(fakeWakeLock.isHeld).isFalse() } - private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService = + @Test + fun testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakes() = + testScope.runTest { + whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(false) + underTest.onAttachedToWindow() + onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>()) + verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) + + // Task fragment becomes empty + onInfoChangedCallback.firstValue.invoke( + mock<TaskFragmentInfo> { on { isEmpty } doReturn true } + ) + advanceUntilIdle() + // Dream is finished and activity is not restarted + verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) + verify(dreamServiceDelegate, never()).wakeUp(any()) + verify(dreamServiceDelegate).finish(any()) + } + + @Test + fun testRestartsActivityWhenRedirectingWakes() = + testScope.runTest { + whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(true) + underTest.onAttachedToWindow() + onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>()) + verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher()) + + // Task fragment becomes empty + onInfoChangedCallback.firstValue.invoke( + mock<TaskFragmentInfo> { on { isEmpty } doReturn true } + ) + advanceUntilIdle() + // Activity is restarted instead of finishing the dream. + verify(taskFragmentComponent, times(2)).startActivityInTaskFragment(intentMatcher()) + verify(dreamServiceDelegate).wakeUp(any()) + verify(dreamServiceDelegate, never()).finish(any()) + } + + private fun intentMatcher() = + argThat<Intent> { + getIntExtra(EXTRA_CONTROLS_SURFACE, CONTROLS_SURFACE_ACTIVITY_PANEL) == + CONTROLS_SURFACE_DREAM + } + + private fun buildService( + activityProvider: DreamServiceDelegate = dreamServiceDelegate + ): HomeControlsDreamService = with(kosmos) { return HomeControlsDreamService( controlsSettingsRepository = FakeControlsSettingsRepository(), taskFragmentFactory = taskFragmentComponentFactory, homeControlsComponentInteractor = homeControlsComponentInteractor, - fakeWakeLockBuilder, - dreamActivityProvider = activityProvider, + wakeLockBuilder = fakeWakeLockBuilder, + dreamServiceDelegate = activityProvider, bgDispatcher = testDispatcher, logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest") ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java index 29fbee01a18b..7936ccc1ddd1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java @@ -108,7 +108,7 @@ public class CommunalTouchHandlerTest extends SysuiTestCase { mTouchHandler.onSessionStart(mTouchSession); verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture()); inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent); - verify(mCentralSurfaces).handleDreamTouch(motionEvent); + verify(mCentralSurfaces).handleCommunalHubTouch(motionEvent); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 26fcb234843d..49d039970a24 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -90,6 +90,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = FakeUserTracker(), + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) settings = FakeSettings() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt index 99a01858471c..9ab1ac116b0d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt @@ -22,13 +22,14 @@ import android.content.SharedPreferences import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.backup.BackupHelper +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -80,6 +81,7 @@ class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() { context = context, userFileManager = userFileManager, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 567e0a9717fc..159ce36bea2d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -21,7 +21,6 @@ import android.content.pm.UserInfo import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig @@ -32,6 +31,7 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient @@ -91,6 +91,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) client1 = FakeCustomizationProviderClient() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt index d630a2f64c5f..6c5001ab9415 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt @@ -136,20 +136,20 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) - fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() = + fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissibleKeyguard() = testScope.runTest { powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() advanceTimeBy(100) // account for debouncing - // We should head back to GONE since we started there. + // We should head to OCCLUDED because keyguard is not dismissible. assertThat(transitionRepository) .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED) } @Test @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) - fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() = + fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissibleKeyguard() = testScope.runTest { kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) powerInteractor.onCameraLaunchGestureDetected() @@ -188,6 +188,7 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { ) // Detect a power gesture and then wake up. + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) reset(transitionRepository) powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() @@ -355,6 +356,7 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { ) // Detect a power gesture and then wake up. + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) reset(transitionRepository) powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index 593cfde2a503..612f2e73e4bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -39,7 +39,7 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags 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.shared.model.CommunalScenes import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -56,13 +56,13 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.testKosmos import junit.framework.Assert.assertEquals -import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.reset import org.mockito.Mockito.spy @@ -122,7 +122,7 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() = testScope.runTest { - kosmos.fakeCommunalRepository.setTransitionState( + kosmos.fakeCommunalSceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) runCurrent() @@ -158,7 +158,7 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop_evenIfIdleOnCommunal() = testScope.runTest { - kosmos.fakeCommunalRepository.setTransitionState( + kosmos.fakeCommunalSceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) runCurrent() @@ -177,6 +177,7 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + @Suppress("ktlint:standard:max-line-length") fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() = testScope.runTest { powerInteractor.onCameraLaunchGestureDetected() @@ -229,6 +230,7 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { ) // Detect a power gesture and then wake up. + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) reset(transitionRepository) powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() @@ -241,6 +243,7 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + @Suppress("ktlint:standard:max-line-length") fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() = testScope.runTest { powerInteractor.setAwakeForTest() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 2d77f4f1c436..5068f6830fa1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -148,6 +148,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = @@ -349,7 +350,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { } @Test - fun quickAffordance_updateOncePerShadeExpansion() = + fun quickAffordance_doNotSendUpdatesWhileShadeExpandingAndStillHidden() = testScope.runTest { val shadeExpansion = MutableStateFlow(0f) whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion) @@ -364,7 +365,9 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { shadeExpansion.value = i / 10f } - assertThat(collectedValue.size).isEqualTo(initialSize + 1) + assertThat(collectedValue[0]) + .isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java) + assertThat(collectedValue.size).isEqualTo(initialSize) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index 2b8a644162c7..9dc930babc10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -27,18 +27,22 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.data.repository.setSceneTransition import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf @@ -192,6 +196,175 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun surfaceBehindVisibility_fromLockscreenToGone_trueThroughout() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Before the transition, we start on Lockscreen so the surface should start invisible. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen)) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isFalse() + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + + // Start the transition to Gone, the surface should become immediately visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = flowOf(0.3f), + currentScene = flowOf(Scenes.Lockscreen), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isTrue() + + // Towards the end of the transition, the surface should continue to be visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = flowOf(0.9f), + currentScene = flowOf(Scenes.Gone), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isTrue() + + // After the transition, settles on Gone. Surface behind should stay visible now. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(isSurfaceBehindVisible).isTrue() + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_fromBouncerToGone_becomesTrue() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Before the transition, we start on Bouncer so the surface should start invisible. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Bouncer)) + kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "") + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isFalse() + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + + // Start the transition to Gone, the surface should remain invisible prior to hitting + // the + // threshold. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Bouncer, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = + flowOf( + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + ), + currentScene = flowOf(Scenes.Bouncer), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isFalse() + + // Once the transition passes the threshold, the surface should become visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Bouncer, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = + flowOf( + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + 0.01f + ), + currentScene = flowOf(Scenes.Gone), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isTrue() + + // After the transition, settles on Gone. Surface behind should stay visible now. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(isSurfaceBehindVisible).isTrue() + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_idleWhileUnlocked_alwaysTrue() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + + listOf( + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Gone, + ) + .forEach { scene -> + kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) + kosmos.sceneInteractor.changeScene(scene, "") + assertThat(currentScene).isEqualTo(scene) + assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") + .that(isSurfaceBehindVisible) + .isTrue() + } + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_idleWhileLocked_alwaysFalse() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + + listOf( + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Lockscreen, + ) + .forEach { scene -> + kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) + kosmos.sceneInteractor.changeScene(scene, "") + assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") + .that(isSurfaceBehindVisible) + .isFalse() + } + } + + @Test @DisableSceneContainer fun testUsingGoingAwayAnimation_duringTransitionToGone() = testScope.runTest { 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/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt index 04c270d07b0a..ad24a711e9b7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.kosmos.testScope @@ -104,6 +105,7 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { burnInInteractor = burnInInteractor, shortcutsCombinedViewModel = shortcutsCombinedViewModel, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, ) } 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/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt index 37d472169ae5..7ebebd7afa91 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt @@ -203,6 +203,21 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { .containsExactlyElementsIn(DEFAULT_TILES.toTileSpecs() + startingTiles) } + @Test + fun prependDefault_noChangesWhenInRetail() = + testScope.runTest { + val user = 0 + retailModeRepository.setRetailMode(true) + val startingTiles = "a" + storeTilesForUser(startingTiles, user) + + runCurrent() + underTest.prependDefault(user) + runCurrent() + + assertThat(loadTilesForUser(user)).isEqualTo(startingTiles) + } + private fun TestScope.storeTilesForUser(specs: String, forUser: Int) { secureSettings.putStringForUser(SETTING, specs, forUser) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt index 58fc10917d44..b12fbc2066a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt @@ -327,6 +327,32 @@ class UserTileSpecRepositoryTest : SysuiTestCase() { assertThat(loadTiles()).isEqualTo(expected) } + @Test + fun setTilesWithRepeats_onlyDistinctTiles() = + testScope.runTest { + val tilesToSet = "a,b,c,a,d,b".toTileSpecs() + val expected = "a,b,c,d" + + val tiles by collectLastValue(underTest.tiles()) + underTest.setTiles(tilesToSet) + + assertThat(tiles).isEqualTo(expected.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(expected) + } + + @Test + fun prependDefaultTwice_doesntAddMoreTiles() = + testScope.runTest { + val tiles by collectLastValue(underTest.tiles()) + underTest.setTiles(listOf(TileSpec.create("a"))) + + underTest.prependDefault() + val currentTiles = tiles!! + underTest.prependDefault() + + assertThat(tiles).isEqualTo(currentTiles) + } + private fun getDefaultTileSpecs(): List<TileSpec> { return defaultTilesRepository.defaultTiles } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt index 311122d7f8d5..16f30feb7e3b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.HearingDevicesTile import com.android.systemui.qs.tiles.OneHandedModeTile import com.android.systemui.qs.tiles.ReduceBrightColorsTile import com.android.systemui.util.mockito.mock @@ -77,6 +78,10 @@ class A11yShortcutAutoAddableListTest : SysuiTestCase() { TileSpec.create(ReduceBrightColorsTile.TILE_SPEC), AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME ), + factory.create( + TileSpec.create(HearingDevicesTile.TILE_SPEC), + AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME + ), ) val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index 1c73fe2b305d..6ad4b317b94c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -49,6 +49,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.qs.tiles.di.NewQSTileFactory import com.android.systemui.qs.toProto +import com.android.systemui.retail.data.repository.FakeRetailModeRepository import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.any @@ -85,6 +86,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { private val pipelineFlags = QSPipelineFlagsRepository() private val tileLifecycleManagerFactory = TLMFactory() private val minimumTilesRepository = MinimumTilesFixedRepository() + private val retailModeRepository = FakeRetailModeRepository() @Mock private lateinit var customTileStatePersister: CustomTileStatePersister @@ -118,6 +120,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { installedTilesComponentRepository = installedTilesPackageRepository, userRepository = userRepository, minimumTilesRepository = minimumTilesRepository, + retailModeRepository = retailModeRepository, customTileStatePersister = customTileStatePersister, tileFactory = tileFactory, newQSTileFactory = { newQSTileFactory }, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt index 260189d401d2..e8ad038f8fbc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt @@ -34,6 +34,8 @@ import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedReposit import com.android.systemui.qs.pipeline.data.repository.fakeDefaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository +import com.android.systemui.qs.pipeline.data.repository.fakeRetailModeRepository +import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.qsTileFactory import com.android.systemui.settings.fakeUserTracker @@ -138,6 +140,19 @@ class NoLowNumberOfTilesTest : SysuiTestCase() { } } + @Test + fun inRetailMode_onlyOneTile_noPrependDefault() = + with(kosmos) { + testScope.runTest { + fakeRetailModeRepository.setRetailMode(true) + fakeTileSpecRepository.setTiles(0, listOf(goodTile)) + val tiles by collectLastValue(currentTilesInteractor.currentTiles) + runCurrent() + + assertThat(tiles!!.map { it.spec }).isEqualTo(listOf(goodTile)) + } + } + private fun tileCreator(spec: String): QSTile? { return if (spec.contains("OEM")) { null // We don't know how to create OEM spec tiles diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt index f1cd0c843256..79e4fef874b6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt @@ -179,6 +179,7 @@ class AlarmTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.status_bar_alarm) return QSTileState( { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) }, + R.drawable.ic_alarm, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt index 6e9db2cbef07..a0d26c28cbfa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt @@ -254,6 +254,7 @@ class BatterySaverTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.battery_detail_switch_title) return QSTileState( { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt index d05e98faee22..ea7b7c5f797d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt @@ -78,6 +78,7 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.quick_settings_color_correction_label) return QSTileState( { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) }, + R.drawable.ic_qs_color_correction, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt index 3972938d7b1b..b4ff56566c75 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt @@ -245,6 +245,7 @@ class CustomTileMapperTest : SysuiTestCase() { ): QSTileState { return QSTileState( { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } }, + null, "test label", activationState, "test subtitle", diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt index b7b3fdbed955..f8e01be5163f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt @@ -66,6 +66,7 @@ class FontScalingTileMapperTest : SysuiTestCase() { null ) }, + R.drawable.ic_qs_font_scaling, context.getString(R.string.quick_settings_font_scaling_label), QSTileState.ActivationState.ACTIVE, null, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt index 39755bf8d764..c44836a70642 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt @@ -70,6 +70,7 @@ class InternetTileMapperTest : SysuiTestCase() { QSTileState.ActivationState.ACTIVE, context.getString(R.string.quick_settings_networks_available), Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null), + wifiRes, context.getString(R.string.quick_settings_internet_label) ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) @@ -96,6 +97,7 @@ class InternetTileMapperTest : SysuiTestCase() { context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!, contentDescription = null ), + R.drawable.ic_qs_no_internet_unavailable, context.getString(R.string.quick_settings_networks_unavailable) ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) @@ -105,11 +107,13 @@ class InternetTileMapperTest : SysuiTestCase() { activationState: QSTileState.ActivationState, secondaryLabel: String, icon: Icon, + iconRes: Int, contentDescription: String, ): QSTileState { val label = context.getString(R.string.quick_settings_internet_label) return QSTileState( { icon }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt index ccd7ed92b884..a7bd69770a4f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt @@ -39,9 +39,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() { private val colorInversionTileConfig = kosmos.qsColorInversionTileConfig private val subtitleArrayId = SubtitleArrayMapping.getSubtitleId(colorInversionTileConfig.tileSpec.spec) - private val subtitleArray by lazy { - context.resources.getStringArray(subtitleArrayId) - } + private val subtitleArray by lazy { context.resources.getStringArray(subtitleArrayId) } // Using lazy (versus =) to make sure we override the right context -- see b/311612168 private val mapper by lazy { ColorInversionTileMapper( @@ -93,6 +91,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.quick_settings_inversion_label) return QSTileState( { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt index 5d2e7013c2f4..75273f2a52e1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt @@ -281,21 +281,16 @@ class NightDisplayTileMapperTest : SysuiTestCase() { secondaryLabel: String? ): QSTileState { val label = context.getString(R.string.quick_settings_night_display_label) - + val iconRes = + if (activationState == QSTileState.ActivationState.ACTIVE) + R.drawable.qs_nightlight_icon_on + else R.drawable.qs_nightlight_icon_off val contentDescription = if (TextUtils.isEmpty(secondaryLabel)) label else TextUtils.concat(label, ", ", secondaryLabel) return QSTileState( - { - Icon.Loaded( - context.getDrawable( - if (activationState == QSTileState.ActivationState.ACTIVE) - R.drawable.qs_nightlight_icon_on - else R.drawable.qs_nightlight_icon_off - )!!, - null - ) - }, + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt index 7ef020da8b67..3189a9e063a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt @@ -97,6 +97,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.quick_settings_onehanded_label) return QSTileState( { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt index d26a21365f54..08e5cbef31ab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt @@ -100,6 +100,7 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() { null ) }, + com.android.systemui.res.R.drawable.ic_qr_code_scanner, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt index 10e9bd695cbc..ca30e9ca3e69 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt @@ -83,17 +83,13 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name) + val iconRes = + if (activationState == QSTileState.ActivationState.ACTIVE) + R.drawable.qs_extra_dim_icon_on + else R.drawable.qs_extra_dim_icon_off return QSTileState( - { - Icon.Loaded( - context.getDrawable( - if (activationState == QSTileState.ActivationState.ACTIVE) - R.drawable.qs_extra_dim_icon_on - else R.drawable.qs_extra_dim_icon_off - )!!, - null - ) - }, + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, context.resources diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt index 60c69f427ef3..04ca38fa4343 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt @@ -172,6 +172,7 @@ class RotationLockTileMapperTest : SysuiTestCase() { val label = context.getString(R.string.quick_settings_rotation_unlocked_label) return QSTileState( { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt index d162c778f607..9bb61415de28 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt @@ -92,6 +92,7 @@ class DataSaverTileMapperTest : SysuiTestCase() { return QSTileState( { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt index 31ae9c5a3930..336b56612261 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt @@ -111,6 +111,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() { return QSTileState( { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt index 5e7aadcda6db..b08f39b0accf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt @@ -147,6 +147,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() { return QSTileState( { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt index a9776068b20c..c021caa598b9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt @@ -70,6 +70,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { ): QSTileState { return QSTileState( { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt index afe7b8f8d50b..7388d51a7cf0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -54,9 +54,11 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyFloat import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest @@ -162,16 +164,25 @@ class QSSceneAdapterImplTest : SysuiTestCase() { with(qsImpl!!) { verify(this).setQsVisible(false) - verify(this) + verify(this, never()) .setQsExpansion( - /* expansion= */ 0f, - /* panelExpansionFraction= */ 1f, - /* proposedTranslation= */ 0f, - /* squishinessFraction= */ 1f, + /* expansion= */ anyFloat(), + /* panelExpansionFraction= */ anyFloat(), + /* proposedTranslation= */ anyFloat(), + /* squishinessFraction= */ anyFloat(), ) verify(this).setListening(false) verify(this).setExpanded(false) } + + underTest.applyLatestExpansionAndSquishiness() + verify(qsImpl!!) + .setQsExpansion( + /* expansion= */ 0f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) } @Test @@ -186,16 +197,25 @@ class QSSceneAdapterImplTest : SysuiTestCase() { underTest.setState(QSSceneAdapter.State.QQS) with(qsImpl!!) { verify(this).setQsVisible(true) - verify(this) + verify(this, never()) .setQsExpansion( - /* expansion= */ 0f, - /* panelExpansionFraction= */ 1f, - /* proposedTranslation= */ 0f, - /* squishinessFraction= */ 1f, + /* expansion= */ anyFloat(), + /* panelExpansionFraction= */ anyFloat(), + /* proposedTranslation= */ anyFloat(), + /* squishinessFraction= */ anyFloat(), ) verify(this).setListening(true) verify(this).setExpanded(false) } + + underTest.applyLatestExpansionAndSquishiness() + verify(qsImpl!!) + .setQsExpansion( + /* expansion= */ 0f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) } @Test @@ -210,16 +230,25 @@ class QSSceneAdapterImplTest : SysuiTestCase() { underTest.setState(QSSceneAdapter.State.QS) with(qsImpl!!) { verify(this).setQsVisible(true) - verify(this) + verify(this, never()) .setQsExpansion( - /* expansion= */ 1f, - /* panelExpansionFraction= */ 1f, - /* proposedTranslation= */ 0f, - /* squishinessFraction= */ 1f, + /* expansion= */ anyFloat(), + /* panelExpansionFraction= */ anyFloat(), + /* proposedTranslation= */ anyFloat(), + /* squishinessFraction= */ anyFloat(), ) verify(this).setListening(true) verify(this).setExpanded(true) } + + underTest.applyLatestExpansionAndSquishiness() + verify(qsImpl!!) + .setQsExpansion( + /* expansion= */ 1f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) } @Test @@ -235,16 +264,25 @@ class QSSceneAdapterImplTest : SysuiTestCase() { underTest.setState(QSSceneAdapter.State.Expanding(progress)) with(qsImpl!!) { verify(this).setQsVisible(true) - verify(this) + verify(this, never()) .setQsExpansion( - /* expansion= */ progress, - /* panelExpansionFraction= */ 1f, - /* proposedTranslation= */ 0f, - /* squishinessFraction= */ 1f, + /* expansion= */ anyFloat(), + /* panelExpansionFraction= */ anyFloat(), + /* proposedTranslation= */ anyFloat(), + /* squishinessFraction= */ anyFloat(), ) verify(this).setListening(true) verify(this).setExpanded(true) } + + underTest.applyLatestExpansionAndSquishiness() + verify(qsImpl!!) + .setQsExpansion( + /* expansion= */ progress, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) } @Test @@ -260,16 +298,25 @@ class QSSceneAdapterImplTest : SysuiTestCase() { underTest.setState(QSSceneAdapter.State.UnsquishingQQS { squishiness }) with(qsImpl!!) { verify(this).setQsVisible(true) - verify(this) + verify(this, never()) .setQsExpansion( - /* expansion= */ 0f, - /* panelExpansionFraction= */ 1f, - /* proposedTranslation= */ 0f, - /* squishinessFraction= */ squishiness, + /* expansion= */ anyFloat(), + /* panelExpansionFraction= */ anyFloat(), + /* proposedTranslation= */ anyFloat(), + /* squishinessFraction= */ anyFloat(), ) verify(this).setListening(true) verify(this).setExpanded(false) } + + underTest.applyLatestExpansionAndSquishiness() + verify(qsImpl!!) + .setQsExpansion( + /* expansion= */ 0f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ squishiness, + ) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt index 8cb811de851b..fed613153a4e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -16,6 +16,10 @@ package com.android.systemui.statusbar +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession +import android.os.fakeExecutorHandler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.notification.NotificationListenerService @@ -54,6 +58,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { private val notifPipeline = kosmos.notifPipeline private val notifCollection = kosmos.mockNotifCollection private val dumpManager = kosmos.dumpManager + private val handler = kosmos.fakeExecutorHandler private val mediaDataManager = mock<MediaDataManager>() private val backgroundExecutor = FakeExecutor(FakeSystemClock()) @@ -72,13 +77,17 @@ class NotificationMediaManagerTest : SysuiTestCase() { mediaDataManager, dumpManager, backgroundExecutor, + handler, ) + val mediaSession = MediaSession(context, "TEST") + notificationMediaManager.mMediaController = + MediaController(context, mediaSession.sessionToken) verify(mediaDataManager).addListener(listenerCaptor.capture()) } @Test - @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT) fun mediaDataRemoved_userInitiated_dismissNotif() { val notifEntryCaptor = argumentCaptor<NotificationEntry>() val notifEntry = mock<NotificationEntry>() @@ -93,7 +102,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT) fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() { listenerCaptor.lastValue.onMediaDataRemoved(KEY, false) @@ -101,7 +110,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DELETEINTENT) fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() { val notifEntryCaptor = argumentCaptor<NotificationEntry>() val notifEntry = mock<NotificationEntry>() @@ -114,4 +123,32 @@ class NotificationMediaManagerTest : SysuiTestCase() { verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any()) assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY) } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION) + fun clearMediaNotification_flagOn_resetMediaMetadata() { + // set up media metadata. + notificationMediaManager.mMediaListener.onMetadataChanged(MediaMetadata.Builder().build()) + backgroundExecutor.runAllReady() + + // clear media notification. + notificationMediaManager.clearCurrentMediaNotification() + backgroundExecutor.runAllReady() + + assertThat(notificationMediaManager.mediaMetadata).isNull() + assertThat(notificationMediaManager.mMediaController).isNull() + } + + @Test + @DisableFlags(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION) + fun clearMediaNotification_flagOff_resetMediaMetadata() { + // set up media metadata. + notificationMediaManager.mMediaListener.onMetadataChanged(MediaMetadata.Builder().build()) + + // clear media notification. + notificationMediaManager.clearCurrentMediaNotification() + + assertThat(notificationMediaManager.mediaMetadata).isNull() + assertThat(notificationMediaManager.mMediaController).isNull() + } } 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/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt index 0ca620751ddf..57d325129fa2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.LaunchableView import com.android.systemui.assist.AssistManager +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.ActivityStarter.OnDismissAction @@ -94,6 +95,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityIntentHelper: ActivityIntentHelper + @Mock private lateinit var communalSceneInteractor: CommunalSceneInteractor private lateinit var underTest: LegacyActivityStarterInternalImpl private val mainExecutor = FakeExecutor(FakeSystemClock()) private val shadeAnimationInteractor = @@ -127,6 +129,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { userTracker = userTracker, activityIntentHelper = activityIntentHelper, mainExecutor = mainExecutor, + communalSceneInteractor = communalSceneInteractor, ) whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER) } @@ -138,7 +141,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { whenever(keyguardStateController.isShowing).thenReturn(true) whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) - underTest.startPendingIntentDismissingKeyguard(pendingIntent) + underTest.startPendingIntentDismissingKeyguard(intent = pendingIntent, dismissShade = true) mainExecutor.runAllReady() verify(statusBarKeyguardViewManager) @@ -232,6 +235,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, + dismissShade = true, intentSentUiThreadCallback = null, associatedView = associatedView, ) @@ -344,6 +348,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { ) { underTest.startPendingIntentDismissingKeyguard( intent = intent, + dismissShade = true, intentSentUiThreadCallback = intentSentUiThreadCallback, animationController = animationController, showOverLockscreen = 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/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt index 63f19fbdfed9..6b5d07282a08 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -78,7 +78,7 @@ class AvalancheControllerTest : SysuiTestCase() { // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of // declaration, where mocks are null - mAvalancheController = AvalancheController(dumpManager) + mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake) testableHeadsUpManager = TestableHeadsUpManager( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 0f66a93bcbec..88bef91d043f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -38,6 +38,8 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.app.Person; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; @@ -146,7 +148,63 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { @Override public void SysuiSetup() throws Exception { super.SysuiSetup(); - mAvalancheController = new AvalancheController(dumpManager); + mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake); + } + + @Test + public void testHasNotifications_headsUpManagerMapNotEmpty_true() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext); + bhum.showNotification(entry); + + assertThat(bhum.mHeadsUpEntryMap).isNotEmpty(); + assertThat(bhum.hasNotifications()).isTrue(); + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testHasNotifications_avalancheMapNotEmpty_true() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, + mContext); + final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry); + mAvalancheController.addToNext(headsUpEntry, () -> {}); + + assertThat(mAvalancheController.getWaitingEntryList()).isNotEmpty(); + assertThat(bhum.hasNotifications()).isTrue(); + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testHasNotifications_false() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + assertThat(bhum.mHeadsUpEntryMap).isEmpty(); + assertThat(mAvalancheController.getWaitingEntryList()).isEmpty(); + assertThat(bhum.hasNotifications()).isFalse(); + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testGetHeadsUpEntryList_includesAvalancheEntryList() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, + mContext); + final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry); + mAvalancheController.addToNext(headsUpEntry, () -> {}); + + assertThat(bhum.getHeadsUpEntryList()).contains(headsUpEntry); + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testGetHeadsUpEntry_returnsAvalancheEntry() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, + mContext); + final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry); + mAvalancheController.addToNext(headsUpEntry, () -> {}); + + assertThat(bhum.getHeadsUpEntry(notifEntry.getKey())).isEqualTo(headsUpEntry); } @Test @@ -553,7 +611,31 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { } @Test - public void testPinEntry_logsPeek() { + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testPinEntry_logsPeek_throttleEnabled() { + final BaseHeadsUpManager hum = createHeadsUpManager(); + + // Needs full screen intent in order to be pinned + final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry( + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext)); + + // Note: the standard way to show a notification would be calling showNotification rather + // than onAlertEntryAdded. However, in practice showNotification in effect adds + // the notification and then updates it; in order to not log twice, the entry needs + // to have a functional ExpandableNotificationRow that can keep track of whether it's + // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit. + hum.onEntryAdded(entryToPin); + + assertEquals(2, mUiEventLoggerFake.numLogs()); + assertEquals(AvalancheController.ThrottleEvent.SHOWN.getId(), + mUiEventLoggerFake.eventId(0)); + assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(), + mUiEventLoggerFake.eventId(1)); + } + + @Test + @DisableFlags(NotificationThrottleHun.FLAG_NAME) + public void testPinEntry_logsPeek_throttleDisabled() { final BaseHeadsUpManager hum = createHeadsUpManager(); // Needs full screen intent in order to be pinned diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java index 9feb914c56e9..200e92e4370b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java @@ -167,7 +167,7 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { mContext.getOrCreateTestableResources().addOverride( R.integer.ambient_notification_extension_time, 500); - mAvalancheController = new AvalancheController(dumpManager); + mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt index cec8ccf96b4a..b83b93b8f77e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt @@ -18,33 +18,26 @@ package com.android.systemui.volume.domain.interactor import android.bluetooth.BluetoothDevice import android.graphics.drawable.TestStubDrawable -import android.media.AudioDeviceInfo -import android.media.AudioDevicePort import android.media.AudioManager import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.R import com.android.settingslib.bluetooth.CachedBluetoothDevice -import com.android.settingslib.media.BluetoothMediaDevice -import com.android.settingslib.media.MediaDevice -import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.bluetooth.bluetoothAdapter import com.android.systemui.bluetooth.cachedBluetoothDeviceManager import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever +import com.android.systemui.volume.data.repository.TestAudioDevicesFactory import com.android.systemui.volume.data.repository.audioRepository import com.android.systemui.volume.data.repository.audioSharingRepository import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.localMediaController import com.android.systemui.volume.localMediaRepository import com.android.systemui.volume.mediaControllerRepository +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.TestMediaDevicesFactory import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -52,6 +45,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) @@ -84,7 +81,7 @@ class AudioOutputInteractorTest : SysuiTestCase() { testScope.runTest { with(audioRepository) { setMode(AudioManager.MODE_IN_CALL) - setCommunicationDevice(builtInDevice) + setCommunicationDevice(TestAudioDevicesFactory.builtInDevice()) } val device by collectLastValue(underTest.currentAudioDevice) @@ -104,7 +101,7 @@ class AudioOutputInteractorTest : SysuiTestCase() { testScope.runTest { with(audioRepository) { setMode(AudioManager.MODE_IN_CALL) - setCommunicationDevice(wiredDevice) + setCommunicationDevice(TestAudioDevicesFactory.wiredDevice()) } val device by collectLastValue(underTest.currentAudioDevice) @@ -122,17 +119,18 @@ class AudioOutputInteractorTest : SysuiTestCase() { fun inCall_bluetooth_returnsCommunicationDevice() { with(kosmos) { testScope.runTest { + val btDevice = TestAudioDevicesFactory.bluetoothDevice() with(audioRepository) { setMode(AudioManager.MODE_IN_CALL) setCommunicationDevice(btDevice) } val bluetoothDevice: BluetoothDevice = mock { - whenever(address).thenReturn(btDevice.address) + on { address }.thenReturn(btDevice.address) } val cachedBluetoothDevice: CachedBluetoothDevice = mock { - whenever(address).thenReturn(btDevice.address) - whenever(name).thenReturn(btDevice.productName.toString()) - whenever(isHearingAidDevice).thenReturn(true) + on { address }.thenReturn(btDevice.address) + on { name }.thenReturn(btDevice.productName.toString()) + on { isHearingAidDevice }.thenReturn(true) } whenever(bluetoothAdapter.getRemoteDevice(eq(btDevice.address))) .thenReturn(bluetoothDevice) @@ -156,7 +154,9 @@ class AudioOutputInteractorTest : SysuiTestCase() { testScope.runTest { audioRepository.setMode(AudioManager.MODE_NORMAL) mediaControllerRepository.setActiveSessions(listOf(localMediaController)) - localMediaRepository.updateCurrentConnectedDevice(builtInMediaDevice) + localMediaRepository.updateCurrentConnectedDevice( + TestMediaDevicesFactory.builtInMediaDevice() + ) val device by collectLastValue(underTest.currentAudioDevice) @@ -175,7 +175,9 @@ class AudioOutputInteractorTest : SysuiTestCase() { testScope.runTest { audioRepository.setMode(AudioManager.MODE_NORMAL) mediaControllerRepository.setActiveSessions(listOf(localMediaController)) - localMediaRepository.updateCurrentConnectedDevice(wiredMediaDevice) + localMediaRepository.updateCurrentConnectedDevice( + TestMediaDevicesFactory.wiredMediaDevice() + ) val device by collectLastValue(underTest.currentAudioDevice) @@ -194,7 +196,9 @@ class AudioOutputInteractorTest : SysuiTestCase() { testScope.runTest { audioRepository.setMode(AudioManager.MODE_NORMAL) mediaControllerRepository.setActiveSessions(listOf(localMediaController)) - localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice) + localMediaRepository.updateCurrentConnectedDevice( + TestMediaDevicesFactory.bluetoothMediaDevice() + ) val device by collectLastValue(underTest.currentAudioDevice) @@ -208,48 +212,8 @@ class AudioOutputInteractorTest : SysuiTestCase() { } private companion object { + val testIcon = TestStubDrawable() - val builtInDevice = - AudioDeviceInfo( - AudioDevicePort.createForTesting( - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, - "built_in", - "" - ) - ) - val wiredDevice = - AudioDeviceInfo( - AudioDevicePort.createForTesting(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, "wired", "") - ) - val btDevice = - AudioDeviceInfo( - AudioDevicePort.createForTesting( - AudioDeviceInfo.TYPE_BLE_HEADSET, - "bt", - "test_address" - ) - ) - val builtInMediaDevice: MediaDevice = - mock<PhoneMediaDevice> { - whenever(name).thenReturn("built_in_media") - whenever(icon).thenReturn(testIcon) - } - val wiredMediaDevice: MediaDevice = - mock<PhoneMediaDevice> { - whenever(deviceType) - .thenReturn(MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE) - whenever(name).thenReturn("wired_media") - whenever(icon).thenReturn(testIcon) - } - val bluetoothMediaDevice: MediaDevice = - mock<BluetoothMediaDevice> { - whenever(name).thenReturn("bt_media") - whenever(icon).thenReturn(testIcon) - val cachedBluetoothDevice: CachedBluetoothDevice = mock { - whenever(isHearingAidDevice).thenReturn(true) - } - whenever(cachedDevice).thenReturn(cachedBluetoothDevice) - } } @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/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 072ec9986c61..de659315e176 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -70,9 +70,11 @@ public interface ActivityStarter { /** * Similar to {@link #startPendingIntentMaybeDismissingKeyguard(PendingIntent, Runnable, * ActivityTransitionAnimator.Controller)}, but also specifies a fill-in intent and extra - * options that could be used to populate the pending intent and launch the activity. + * option that could be used to populate the pending intent and launch the activity. This also + * allows the caller to avoid dismissing the shade. */ void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent, + boolean dismissShade, @Nullable Runnable intentSentUiThreadCallback, @Nullable ActivityTransitionAnimator.Controller animationController, @Nullable Intent fillInIntent, diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index 3244eb43c8c4..bf58eee9a9ce 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -94,6 +94,9 @@ public interface QS extends FragmentBase { default void setHasNotifications(boolean hasNotifications) { } + /** Sets whether the squishiness fraction should be updated on the media host. */ + default void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {} + /** * Should touches from the notification panel be disallowed? * The notification panel might grab any touches rom QS at any time to collapse the shade. diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml deleted file mode 100644 index 4a2a1cb9dc6d..000000000000 --- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** Copyright 2022, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. ---> - -<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/footer_actions_height" - android:elevation="@dimen/qs_panel_elevation" - android:paddingTop="@dimen/qs_footer_actions_top_padding" - android:paddingBottom="@dimen/qs_footer_actions_bottom_padding" - android:background="@drawable/qs_footer_actions_background" - android:gravity="center_vertical|end" - android:layout_gravity="bottom" -/>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml deleted file mode 100644 index fad41c822ec0..000000000000 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<com.android.systemui.statusbar.AlphaOptimizedFrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="@dimen/qs_footer_action_button_size" - android:layout_height="@dimen/qs_footer_action_button_size" - android:visibility="gone"> - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:layout_gravity="center" - android:scaleType="centerInside" /> -</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml deleted file mode 100644 index c09607d19bdd..000000000000 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml +++ /dev/null @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<com.android.systemui.statusbar.AlphaOptimizedFrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="@dimen/qs_footer_action_button_size" - android:layout_height="@dimen/qs_footer_action_button_size" - android:background="@drawable/qs_footer_action_circle" - android:visibility="gone"> - <TextView - android:id="@+id/number" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:layout_gravity="center" - android:textColor="?attr/onShadeInactiveVariant" - android:textSize="18sp"/> - <ImageView - android:id="@+id/new_dot" - android:layout_width="12dp" - android:layout_height="12dp" - android:scaleType="fitCenter" - android:layout_gravity="bottom|end" - android:src="@drawable/fgs_dot" - android:contentDescription="@string/fgs_dot_content_description" /> -</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml deleted file mode 100644 index 1c31f1da0681..000000000000 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml +++ /dev/null @@ -1,66 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<com.android.systemui.animation.view.LaunchableLinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="0dp" - android:layout_height="@dimen/qs_security_footer_single_line_height" - android:layout_weight="1" - android:orientation="horizontal" - android:paddingHorizontal="@dimen/qs_footer_padding" - android:gravity="center_vertical" - android:layout_marginEnd="@dimen/qs_footer_action_inset" - android:background="@drawable/qs_security_footer_background" - android:visibility="gone"> - <ImageView - android:id="@+id/icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:gravity="start" - android:layout_marginEnd="12dp" - android:contentDescription="@null" - android:src="@drawable/ic_info_outline" - android:tint="?attr/onSurfaceVariant" /> - - <TextView - android:id="@+id/text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:maxLines="1" - android:ellipsize="end" - android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:textColor="?attr/onSurfaceVariant"/> - - <ImageView - android:id="@+id/new_dot" - android:layout_width="12dp" - android:layout_height="12dp" - android:scaleType="fitCenter" - android:src="@drawable/fgs_dot" - android:contentDescription="@string/fgs_dot_content_description" - /> - - <ImageView - android:id="@+id/chevron_icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:layout_marginStart="8dp" - android:contentDescription="@null" - android:src="@*android:drawable/ic_chevron_end" - android:autoMirrored="true" - android:tint="?attr/onSurfaceVariant" /> -</com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml b/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml deleted file mode 100644 index a8abd793bd00..000000000000 --- a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 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. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" /> - <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" /> - <item android:color="@color/transparent" /> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml deleted file mode 100644 index 0881d7c5c2b5..000000000000 --- a/packages/SystemUI/res/drawable/fgs_dot.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** Copyright 2022, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="oval" - android:width="12dp" - android:height="12dp"> - <solid android:color="?attr/tertiary" /> -</shape>
\ No newline at end of file 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/drawable/ic_widgets.xml b/packages/SystemUI/res/drawable/ic_widgets.xml new file mode 100644 index 000000000000..b21d047f9386 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_widgets.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- go/gm2-icons, from gs_widgets_vd_theme_24.xml --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/black" + android:pathData="M666,520L440,294L666,68L892,294L666,520ZM120,440L120,120L440,120L440,440L120,440ZM520,840L520,520L840,520L840,840L520,840ZM120,840L120,520L440,520L440,840L120,840ZM200,360L360,360L360,200L200,200L200,360ZM667,408L780,295L667,182L554,295L667,408ZM600,760L760,760L760,600L600,600L600,760ZM200,760L360,760L360,600L200,600L200,760ZM360,360L360,360L360,360L360,360L360,360ZM554,295L554,295L554,295L554,295L554,295ZM360,600L360,600L360,600L360,600L360,600ZM600,600L600,600L600,600L600,600L600,600Z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml deleted file mode 100644 index 4a5d4af96497..000000000000 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:inset="@dimen/qs_footer_action_inset"> - <ripple - android:color="?android:attr/colorControlHighlight"> - <item android:id="@android:id/mask"> - <!-- We make this shape a rounded rectangle instead of a oval so that it can animate --> - <!-- properly into an app/dialog. --> - <shape android:shape="rectangle"> - <solid android:color="@android:color/white"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - <item> - <shape android:shape="rectangle"> - <solid android:color="?attr/shadeInactive"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - - </ripple> -</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml deleted file mode 100644 index 47a2965bcfac..000000000000 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:inset="@dimen/qs_footer_action_inset"> - <ripple - android:color="?android:attr/colorControlHighlight"> - <item android:id="@android:id/mask"> - <!-- We make this shape a rounded rectangle instead of a oval so that it can animate --> - <!-- properly into an app/dialog. --> - <shape android:shape="rectangle"> - <solid android:color="@android:color/white"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - <item> - <shape android:shape="rectangle"> - <solid android:color="?attr/shadeActive"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - <item> - <shape android:shape="rectangle"> - <solid android:color="@color/qs_footer_power_button_overlay_color"/> - <corners android:radius="@dimen/qs_footer_action_corner_radius"/> - </shape> - </item> - - </ripple> -</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml new file mode 100644 index 000000000000..627b92b8a779 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 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. + --> + +<ripple + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:color="?android:attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle"> + <solid android:color="@android:color/transparent"/> + <corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius"/> + <stroke + android:width="1dp" + android:color="?androidprv:attr/textColorTertiary" /> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml deleted file mode 100644 index 0b0055b1f020..000000000000 --- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 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. - --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:insetTop="@dimen/qs_footer_action_inset" - android:insetBottom="@dimen/qs_footer_action_inset" - > - <ripple - android:color="?android:attr/colorControlHighlight"> - <item android:id="@android:id/mask"> - <shape android:shape="rectangle"> - <solid android:color="@android:color/white"/> - <corners android:radius="@dimen/qs_security_footer_corner_radius"/> - </shape> - </item> - <item> - <shape android:shape="rectangle"> - <stroke android:width="1dp" - android:color="?attr/shadeInactive"/> - <corners android:radius="@dimen/qs_security_footer_corner_radius"/> - </shape> - </item> - </ripple> -</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml b/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml deleted file mode 100644 index 29a014a713f7..000000000000 --- a/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 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. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorAccentPrimary" /> - <corners android:radius="40dp" /> -</shape> diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml deleted file mode 100644 index 8b9eabc5bd93..000000000000 --- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml +++ /dev/null @@ -1,237 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/biometric_prompt_constraint_layout" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ImageView - android:id="@+id/background" - android:layout_width="0dp" - android:layout_height="0dp" - android:contentDescription="@string/biometric_dialog_empty_space_description" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <View - android:id="@+id/panel" - style="@style/AuthCredentialPanelStyle" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/rightGuideline" - app:layout_constraintStart_toStartOf="@+id/leftGuideline" - app:layout_constraintTop_toTopOf="@+id/topBarrier" - app:layout_constraintWidth_max="640dp" /> - - <include - android:id="@+id/button_bar" - layout="@layout/biometric_prompt_button_bar" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="@id/bottomGuideline" - app:layout_constraintEnd_toEndOf="@id/panel" - app:layout_constraintStart_toStartOf="@id/panel" /> - - <ScrollView - android:id="@+id/scrollView" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:fadeScrollbars="false" - android:fillViewport="true" - android:paddingBottom="32dp" - android:paddingHorizontal="32dp" - android:paddingTop="24dp" - app:layout_constrainedHeight="true" - app:layout_constrainedWidth="true" - app:layout_constraintBottom_toTopOf="@+id/scrollBarrier" - app:layout_constraintEnd_toEndOf="@id/panel" - app:layout_constraintStart_toStartOf="@id/panel" - app:layout_constraintTop_toTopOf="@+id/topGuideline" - app:layout_constraintVertical_bias="1.0"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/innerConstraint" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <ImageView - android:id="@+id/logo" - android:layout_width="@dimen/biometric_prompt_logo_size" - android:layout_height="@dimen/biometric_prompt_logo_size" - android:layout_gravity="center" - android:contentDescription="@string/biometric_dialog_logo" - android:scaleType="fitXY" - android:visibility="visible" - app:layout_constraintBottom_toTopOf="@+id/logo_description" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <TextView - android:id="@+id/logo_description" - style="@style/TextAppearance.AuthCredential.LogoDescription" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="16dp" - app:layout_constraintBottom_toTopOf="@+id/title" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/logo" /> - - <TextView - android:id="@+id/title" - style="@style/TextAppearance.AuthCredential.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:paddingTop="16dp" - app:layout_constraintBottom_toTopOf="@+id/subtitle" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/logo_description" /> - - <TextView - android:id="@+id/subtitle" - style="@style/TextAppearance.AuthCredential.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:paddingTop="16dp" - app:layout_constraintBottom_toTopOf="@+id/contentBarrier" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/title" /> - - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingTop="24dp" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" /> - - <TextView - android:id="@+id/description" - style="@style/TextAppearance.AuthCredential.Description" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:paddingTop="16dp" - android:textAlignment="viewStart" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" /> - - <androidx.constraintlayout.widget.Barrier - android:id="@+id/contentBarrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierAllowsGoneWidgets="false" - app:barrierDirection="top" - app:constraint_referenced_ids="description, customized_view_container" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - </ScrollView> - - <!-- Cancel Button, replaces negative button when biometric is accepted --> - <TextView - android:id="@+id/indicator" - style="@style/TextAppearance.AuthCredential.Indicator" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="24dp" - android:accessibilityLiveRegion="assertive" - android:fadingEdge="horizontal" - android:gravity="center_horizontal" - android:scrollHorizontally="true" - app:layout_constraintBottom_toTopOf="@+id/button_bar" - app:layout_constraintEnd_toEndOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" - app:layout_constraintTop_toBottomOf="@+id/biometric_icon" - app:layout_constraintVertical_bias="0.0" /> - - <!-- "Use Credential" Button, replaces if device credential is allowed --> - - <!-- Positive Button --> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/topBarrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierAllowsGoneWidgets="false" - app:barrierDirection="top" - app:constraint_referenced_ids="scrollView" /> - - <!-- Try Again Button --> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/scrollBarrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierAllowsGoneWidgets="false" - app:barrierDirection="top" - app:constraint_referenced_ids="biometric_icon, button_bar" /> - - <!-- Guidelines for setting panel border --> - <androidx.constraintlayout.widget.Guideline - android:id="@+id/leftGuideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" /> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/rightGuideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/bottomGuideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - app:layout_constraintGuide_end="40dp" /> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/topGuideline" - android:layout_width="0dp" - android:layout_height="0dp" - android:orientation="horizontal" - app:layout_constraintGuide_begin="56dp" /> - - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="1.0" - tools:srcCompat="@tools:sample/avatars" /> - - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon_overlay" - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_gravity="center" - android:contentDescription="@null" - android:scaleType="fitXY" - android:importantForAccessibility="no" - app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" - app:layout_constraintEnd_toEndOf="@+id/biometric_icon" - app:layout_constraintStart_toStartOf="@+id/biometric_icon" - app:layout_constraintTop_toTopOf="@+id/biometric_icon" /> - -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml index 9b5b59fc116f..8d50bfa00fd5 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml @@ -22,9 +22,11 @@ android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@id/rightGuideline" + app:layout_constraintEnd_toStartOf="@id/rightGuideline" app:layout_constraintStart_toStartOf="@id/leftGuideline" - app:layout_constraintTop_toTopOf="@id/topBarrier" /> + app:layout_constraintTop_toTopOf="@id/topBarrier" + app:layout_constraintWidth_max="@dimen/biometric_prompt_panel_max_width" /> + <include android:id="@+id/button_bar" @@ -41,8 +43,8 @@ android:layout_height="wrap_content" android:fillViewport="true" android:fadeScrollbars="false" - android:paddingBottom="24dp" - android:paddingHorizontal="24dp" + android:paddingBottom="@dimen/biometric_prompt_top_scroll_view_bottom_padding" + android:paddingHorizontal="@dimen/biometric_prompt_top_scroll_view_horizontal_padding" android:paddingTop="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" @@ -76,7 +78,7 @@ style="@style/TextAppearance.AuthCredential.LogoDescription" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" + android:paddingTop="@dimen/biometric_prompt_logo_description_top_padding" app:layout_constraintBottom_toTopOf="@+id/title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -180,14 +182,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_begin="0dp" /> + app:layout_constraintGuide_begin="@dimen/biometric_prompt_one_pane_medium_horizontal_guideline_padding" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/rightGuideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_end="0dp" /> + app:layout_constraintGuide_end="@dimen/biometric_prompt_one_pane_medium_horizontal_guideline_padding" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/bottomGuideline" @@ -201,7 +203,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:orientation="horizontal" - app:layout_constraintGuide_begin="119dp" /> + app:layout_constraintGuide_begin="@dimen/biometric_prompt_one_pane_medium_top_guideline_padding" /> <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper android:id="@+id/biometric_icon" diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml index 01b9f7e2e38a..01b9f7e2e38a 100644 --- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml index f45cc7c464d5..be063a9631e8 100644 --- a/packages/SystemUI/res/layout/people_space_activity.xml +++ b/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml @@ -1,5 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2020 The Android Open Source Project + ~ Copyright (C) 2024 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. @@ -12,12 +13,14 @@ ~ 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. - --> -<FrameLayout +--> +<com.android.systemui.animation.view.LaunchableImageView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at - runtime depending on the number of conversations to show. --> -</FrameLayout> + android:layout_height="@dimen/dream_overlay_bottom_affordance_height" + android:layout_width="@dimen/dream_overlay_bottom_affordance_width" + android:layout_gravity="bottom|start" + android:padding="@dimen/dream_overlay_bottom_affordance_padding" + android:scaleType="fitCenter" + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/ic_widgets" + android:contentDescription="@string/accessibility_action_open_communal_hub" /> 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..4a7bef9f48b9 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,22 +35,28 @@ 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:layout_marginTop="@dimen/hearing_devices_layout_margin" + android:minHeight="@dimen/hearing_devices_preset_spinner_height" 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 - android:id="@+id/device_barrier" + android:id="@+id/device_function_barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" + app:barrierAllowsGoneWidgets="false" app:barrierDirection="bottom" app:constraint_referenced_ids="device_list,preset_spinner" /> @@ -66,7 +71,8 @@ android:contentDescription="@string/accessibility_hearing_device_pair_new_device" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/device_barrier" + app:layout_constraintTop_toBottomOf="@id/device_function_barrier" + app:layout_constraintBottom_toTopOf="@id/related_tools_scroll" android:drawableStart="@drawable/ic_add" android:drawablePadding="20dp" android:drawableTint="?android:attr/textColorPrimary" @@ -78,4 +84,32 @@ android:maxLines="1" android:ellipsize="end" /> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/device_barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierAllowsGoneWidgets="false" + app:barrierDirection="bottom" + app:constraint_referenced_ids="device_function_barrier, pair_new_device_button" /> + + <HorizontalScrollView + android:id="@+id/related_tools_scroll" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin" + android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin" + android:layout_marginTop="@dimen/hearing_devices_layout_margin" + android:scrollbars="none" + android:fillViewport="true" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/preset_spinner"> + <LinearLayout + android:id="@+id/related_tools_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + </LinearLayout> + </HorizontalScrollView> + </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml new file mode 100644 index 000000000000..84462d08d4a0 --- /dev/null +++ b/packages/SystemUI/res/layout/hearing_tool_item.xml @@ -0,0 +1,53 @@ +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tool_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center" + android:focusable="true" + android:clickable="true" + android:layout_weight="1"> + <FrameLayout + android:id="@+id/icon_frame" + android:layout_width="@dimen/hearing_devices_tool_icon_frame_width" + android:layout_height="@dimen/hearing_devices_tool_icon_frame_height" + android:background="@drawable/qs_hearing_devices_related_tools_background" + android:focusable="false" > + <ImageView + android:id="@+id/tool_icon" + android:layout_width="@dimen/hearing_devices_tool_icon_size" + android:layout_height="@dimen/hearing_devices_tool_icon_size" + android:layout_gravity="center" + android:scaleType="fitCenter" + android:focusable="false" /> + </FrameLayout> + <TextView + android:id="@+id/tool_name" + android:textDirection="locale" + android:textAlignment="center" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/hearing_devices_layout_margin" + android:ellipsize="end" + android:maxLines="1" + android:textSize="12sp" + android:textAppearance="@style/TextAppearance.Dialog.Body.Message" + android:focusable="false" /> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml index a33be12a655a..cd5c37d43633 100644 --- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml @@ -40,6 +40,7 @@ <ImageView android:src="@*android:drawable/ic_phone" + android:id="@+id/ongoing_activity_chip_icon" android:layout_width="@dimen/ongoing_activity_chip_icon_size" android:layout_height="@dimen/ongoing_activity_chip_icon_size" android:tint="?android:attr/colorPrimary" diff --git a/packages/SystemUI/res/layout/people_space_activity_list_divider.xml b/packages/SystemUI/res/layout/people_space_activity_list_divider.xml deleted file mode 100644 index 3b9fb3be3814..000000000000 --- a/packages/SystemUI/res/layout/people_space_activity_list_divider.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2021 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. ---> -<View - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="2dp" - android:background="?android:attr/colorBackground" /> diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml deleted file mode 100644 index a97c90c5e8ac..000000000000 --- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml +++ /dev/null @@ -1,79 +0,0 @@ -<!-- - ~ Copyright (C) 2021 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. - --> -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/top_level_no_conversations" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:padding="24dp" - android:clipToOutline="true"> - <TextView - android:id="@+id/select_conversation_title" - android:gravity="center" - android:text="@string/select_conversation_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="24sp" - android:layout_alignParentTop="true" /> - - <TextView - android:id="@+id/select_conversation" - android:gravity="center" - android:text="@string/no_conversations_text" - android:layout_width="match_parent" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="16sp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:padding="24dp" - android:layout_marginTop="26dp" - android:layout_below="@id/select_conversation_title"/> - - <Button - style="?android:attr/buttonBarButtonStyle" - android:id="@+id/got_it_button" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:background="@drawable/rounded_bg_full_large_radius" - android:text="@string/got_it" - android:textColor="?androidprv:attr/textColorOnAccent" - android:layout_marginBottom="60dp" - android:layout_alignParentBottom="true" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_above="@id/got_it_button" - android:layout_below="@id/select_conversation" - android:layout_centerInParent="true" - android:clipToOutline="true"> - <LinearLayout - android:id="@+id/widget_initial_layout" - android:layout_width="200dp" - android:layout_height="100dp" - android:layout_gravity="center" - android:background="@drawable/rounded_bg_full_large_radius" - android:layout_above="@id/got_it_button"> - <include layout="@layout/people_space_placeholder_layout" /> - </LinearLayout> - </LinearLayout> -</RelativeLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml deleted file mode 100644 index 2384963c44db..000000000000 --- a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml +++ /dev/null @@ -1,115 +0,0 @@ -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/top_level_with_conversations" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="8dp"> - <TextView - android:id="@+id/select_conversation_title" - android:text="@string/select_conversation_title" - android:gravity="center" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="24sp"/> - - <TextView - android:id="@+id/select_conversation" - android:text="@string/select_conversation_text" - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:textColor="?android:attr/textColorPrimary" - android:textSize="16sp" - android:paddingVertical="24dp" - android:paddingHorizontal="48dp"/> - - <androidx.core.widget.NestedScrollView - android:id="@+id/scroll_view" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <LinearLayout - android:id="@+id/scroll_layout" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:orientation="vertical"> - - <LinearLayout - android:id="@+id/priority" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="35dp"> - <TextView - android:id="@+id/priority_header" - android:text="@string/priority_conversations" - android:layout_width="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:textColor="?androidprv:attr/colorAccentPrimaryVariant" - android:textSize="14sp" - android:paddingStart="16dp" - android:layout_height="wrap_content"/> - - <LinearLayout - android:id="@+id/priority_tiles" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="10dp" - android:orientation="vertical" - android:background="@drawable/rounded_bg_full_large_radius" - android:clipToOutline="true"> - </LinearLayout> - </LinearLayout> - - <LinearLayout - android:id="@+id/recent" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TextView - android:id="@+id/recent_header" - android:gravity="start" - android:text="@string/recent_conversations" - android:layout_width="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:textColor="?androidprv:attr/colorAccentPrimaryVariant" - android:textSize="14sp" - android:paddingStart="16dp" - android:layout_height="wrap_content"/> - - <LinearLayout - android:id="@+id/recent_tiles" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="10dp" - android:orientation="vertical" - android:background="@drawable/rounded_bg_full_large_radius" - android:clipToOutline="true"> - </LinearLayout> - </LinearLayout> - </LinearLayout> - </androidx.core.widget.NestedScrollView> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml deleted file mode 100644 index b0599caae6df..000000000000 --- a/packages/SystemUI/res/layout/people_space_tile_view.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ 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. - --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/tile_view" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <LinearLayout - android:orientation="vertical" - android:background="?androidprv:attr/colorSurface" - android:padding="12dp" - android:elevation="4dp" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <LinearLayout - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="start"> - - <ImageView - android:id="@+id/tile_view_person_icon" - android:layout_width="@dimen/avatar_size_for_medium" - android:layout_height="@dimen/avatar_size_for_medium" /> - - <LinearLayout - android:orientation="horizontal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical"> - - <TextView - android:id="@+id/tile_view_name" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" - android:paddingHorizontal="16dp" - android:textSize="22sp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical"/> - </LinearLayout> - </LinearLayout> - </LinearLayout> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index e3c5a7d03d2e..5f77f61d805b 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -47,13 +47,12 @@ <include layout="@layout/quick_status_bar_expanded_header" /> - <include - layout="@layout/footer_actions" + <androidx.compose.ui.platform.ComposeView android:id="@+id/qs_footer_actions" android:layout_height="@dimen/footer_actions_height" android:layout_width="match_parent" android:layout_gravity="bottom" - /> + android:elevation="@dimen/qs_panel_elevation" /> <include android:id="@+id/qs_customize" 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-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index abbb4cb6bae0..63aa97f542ae 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"het \'n prent gestuur"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Stoor tans skermkiekie..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Stoor tans skermskoot in werkprofiel …"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Stoor tans skermskoot in privaat"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skermkiekie is gestoor"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Kon nie skermkiekie stoor nie"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Eksterne skerm"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Bind nuwe toestel saam"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klik om nuwe toestel saam te bind"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Kon nie voorafstelling opdateer nie"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Deblokkeer toestelmikrofoon?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Deblokkeer toestelkamera?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Deblokkeer toestelkamera en mikrofoon?"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 3bdf212dc464..58940bb27d95 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ምስል ተልኳል"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ቅጽበታዊ ገፅ ዕይታ በማስቀመጥ ላይ..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ቅጽበታዊ ገፅ እይታን ወደ የስራ መገለጫ በማስቀመጥ ላይ…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ቅጽበታዊ ገጽ ዕይታን ወደ ግል በማስቀመጥ ላይ"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ቅጽበታዊ ገፅ ዕይታ ተቀምጧል"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"ቅጽበታዊ ገፅ ዕይታን ማስቀመጥ አልተቻለም"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ውጫዊ ማሳያ"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"አዲስ መሣሪያ ያጣምሩ"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"አዲስ መሣሪያ ለማጣመር ጠቅ ያድርጉ"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ቅድመ-ቅምጥን ማዘመን አልተቻለም"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"የመሣሪያ ማይክሮፎን እገዳ ይነሳ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"የመሣሪያ ካሜራ እገዳ ይነሳ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"የመሣሪያ ካሜራ እና ማይክሮፎን እገዳ ይነሳ?"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index ac210e219f7e..b74b6cc6759e 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"أرسَل صورة"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"جارٍ حفظ لقطة الشاشة..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"جارٍ حفظ لقطة الشاشة في ملف العمل…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"يتم حاليًا حفظ لقطة الشاشة في الملف الشخصي الخاص"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"تم حفظ لقطة الشاشة."</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"تعذّر حفظ لقطة الشاشة"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"الشاشة الخارجية"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"إقران جهاز جديد"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"انقر لإقران جهاز جديد"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"تعذَّر تعديل الإعداد المسبق"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"هل تريد إزالة حظر ميكروفون الجهاز؟"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"هل تريد إزالة حظر كاميرا الجهاز؟"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"هل تريد إزالة حظر الكاميرا والميكروفون؟"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 45363ddfbd9b..e25a19e7f128 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"এখন প্ৰতিচ্ছবি পঠিয়াইছে"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"স্ক্ৰীনশ্বট ছেভ কৰি থকা হৈছে…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"কৰ্মস্থানৰ প্ৰ’ফাইলত স্ক্ৰীনশ্বট ছেভ কৰি থকা হৈছে…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ব্যক্তিগত প্ৰ’ফাইলত স্ক্ৰীনশ্বট ছেভ কৰি থকা হৈছে"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্ৰীনশ্বট ছেভ কৰা হ’ল"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্ৰীনশ্বট ছেভ কৰিব পৰা নগ\'ল"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"বাহ্যিক ডিছপ্লে’"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"নতুন ডিভাইচ পেয়াৰ কৰক"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"নতুন ডিভাইচ পেয়াৰ কৰিবলৈ ক্লিক কৰক"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"প্ৰিছেট আপডে’ট কৰিব পৰা নগ’ল"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ডিভাইচৰ মাইক্ৰ\'ফ\'ন অৱৰোধৰ পৰা আঁতৰাবনে?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ডিভাইচৰ কেমেৰা অৱৰোধৰ পৰা আঁতৰাবনে?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ডিভাইচৰ কেমেৰা আৰু মাইক্ৰ\'ফ\'ন অৱৰোধৰ পৰা আঁতৰাবনে?"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index e796364aceaa..29fb86e00fa3 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"şəkil göndərdi"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Skrinşot yadda saxlanır..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"İş profili skrinşotu saxlanılır…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Skrinşot şəxsidə saxlanılır"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinşot yadda saxlandı"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinşotu yadda saxlamaq alınmadı"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Xarici displey"</string> @@ -271,7 +272,7 @@ <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Toxunaraq cihaza qoşulun, yaxud əlaqəni ayırın"</string> <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Yeni cihaz birləşdirin"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Hamısına baxın"</string> - <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth aç"</string> + <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-u açın"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Qoşulub"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio paylaşma"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Yadda saxlandı"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Yeni cihaz birləşdirin"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Yeni cihaz birləşdirmək üçün klikləyin"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Hazır ayar güncəllənmədi"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Cihaz mikrofonu blokdan çıxarılsın?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Cihaz kamerası blokdan çıxarılsın?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Cihaz kamerası və mikrofonu blokdan çıxarılsın?"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 23a09cad9f53..f7c252ea94e4 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"je poslao/la sliku"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Čuvanje snimka ekrana..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Snimak ekrana se čuva na poslovnom profilu…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Snimak ekrana se čuva na privatnom profilu"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Čuvanje snimka ekrana nije uspelo"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Spoljni ekran"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Upari novi uređaj"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknite da biste uparili nov uređaj"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ažuriranje zadatih podešavanja nije uspelo"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite da odblokirate mikrofon uređaja?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Želite da odblokirate kameru uređaja?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Želite da odblokirate kameru i mikrofon uređaja?"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 7dc9480796db..60084389d9fb 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"адпраўлены відарыс"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Захаванне скрыншота..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Захаванне здымка экрана ў працоўны профіль…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Здымак экрана захоўваецца ў прыватны профіль"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Здымак экрана захаваны"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Не атрымалася зрабіць здымак экрана"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Знешні дысплэй"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Спалучыць новую прыладу"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Націсніце, каб спалучыць новую прыладу"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Не ўдалося абнавіць набор налад"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Разблакіраваць мікрафон прылады?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Разблакіраваць камеру прылады?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Разблакіраваць камеру і мікрафон прылады?"</string> @@ -710,7 +713,7 @@ <string name="notification_more_settings" msgid="4936228656989201793">"Дадатковыя налады"</string> <string name="notification_app_settings" msgid="8963648463858039377">"Наладзіць"</string> <string name="notification_conversation_bubble" msgid="2242180995373949022">"Паказаць усплывальнае апавяшчэнне"</string> - <string name="notification_conversation_unbubble" msgid="6908427185031099868">"Выдаліць усплывальныя апавяшчэнні"</string> + <string name="notification_conversation_unbubble" msgid="6908427185031099868">"Выдаліць усплывальныя чаты"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"кіраванне апавяшчэннямі"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"параметры адкладвання апавяшчэнняў"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 42265b00af30..efaa31f502bd 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"изпратено изображение"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Екранната снимка се запазва..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Екранната снимка се запазва в служебния профил…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Екранната снимка се запазва в личния потребителски профил"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Екранната снимка е запазена"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Не можа да се запази екранна снимка"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Външен екран"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Сдвояване на ново устройство"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Кликнете за сдвояване на ново устройство"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Предварително зададените настройки не бяха актуализирани"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Да се отблокира ли микрофонът на устройството?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Да се отблокира ли камерата на устройството?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Да се отблокират ли камерата и микрофонът на устройството?"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 3d008cb0f3c2..6decb6169419 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"একটি ছবি পাঠানো হয়েছে"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"স্ক্রিনশট সেভ করা হচ্ছে..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"অফিস প্রোফাইলে স্ক্রিনশট সেভ করা হচ্ছে…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ব্যক্তিগত প্রোফাইলে স্ক্রিনশট সেভ করা হয়েছে"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্রিনশট সেভ করা হয়েছে"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্রিনশট সেভ করা যায়নি"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"এক্সটার্নাল ডিসপ্লে"</string> @@ -277,7 +278,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"সেভ করা আছে"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ডিসকানেক্ট করুন"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"চালু করুন"</string> - <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"আগামীকাল অটোমেটিক আবার চালু হবে"</string> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"আগামীকাল আবার অটোমেটিক চালু হবে"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"দ্রুত শেয়ার ও Find My Device-এর মতো ফিচার ব্লুটুথ ব্যবহার করে"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ব্লুটুথ আগামীকাল সকালে চালু হয়ে যাবে"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"অডিও শেয়ারিং"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"নতুন ডিভাইস পেয়ার করুন"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"নতুন ডিভাইস পেয়ার করতে ক্লিক করুন"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"প্রিসেট আপডেট করা যায়নি"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ডিভাইসের মাইক্রোফোন আনব্লক করতে চান?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ডিভাইসের ক্যামেরা আনব্লক করতে চান?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ডিভাইসের ক্যামেরা এবং মাইক্রোফোন আনব্লক করতে চান?"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 96b83c189aa1..1aea98254a2e 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"je poslao/la sliku"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Spašavanje snimka ekrana..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pohranjivanje snimka ekrana na radni profil…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Pohranjivanje snimka ekrana na privatni profil"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Nije moguće sačuvati snimak ekrana"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Vanjski ekran"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uparite novi uređaj"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknite da uparite novi uređaj"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ažuriranje zadane postavke nije uspjelo"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Deblokirati mikrofon uređaja?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Deblokirati kameru uređaja?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Deblokirati kameru i mikrofon uređaja?"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index cf9717f7d4a7..8229e4b4c7ac 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ha enviat una imatge"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"S\'està desant la captura de pantalla..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"S\'està desant la captura al perfil de treball…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"S\'està desant la captura de pantalla al perfil privat"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"S\'ha desat la captura de pantalla"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"No s\'ha pogut desar la captura de pantalla"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Pantalla externa"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Vincula un dispositiu nou"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Fes clic per vincular un dispositiu nou"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"No s\'ha pogut actualitzar el valor predefinit"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vols desbloquejar el micròfon del dispositiu?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vols desbloquejar la càmera del dispositiu?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Vols desbloquejar la càmera i el micròfon del dispositiu?"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index a5a88d412a81..85cef4b65426 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"odesílá obrázek"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Ukládání snímku obrazovky..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukládání snímku obrazovky do pracovního profilu…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Ukládání snímku obrazovky do soukromého profilu"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snímek obrazovky byl uložen"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Snímek obrazovky se nepodařilo uložit"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Externí displej"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Spárovat nové zařízení"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknutím spárujete nové zařízení"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Předvolbu nelze aktualizovat"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Odblokovat mikrofon zařízení?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Odblokovat fotoaparát zařízení?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Odblokovat fotoaparát a mikrofon zařízení?"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 55a5de9a3e24..836fe26c7992 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"sendte et billede"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Gemmer screenshot..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gemmer screenshot på din arbejdsprofil…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Gemmer screenshottet på din private profil"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshottet blev gemt"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshottet kunne ikke gemmes"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Ekstern skærm"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Par ny enhed"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klik for at parre en ny enhed"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Forindstillingen kunne ikke opdateres"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vil du fjerne blokeringen af enhedens mikrofon?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vil du fjerne blokeringen af enhedens kamera?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Vil du fjerne blokeringen af enhedens kamera og mikrofon?"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 1f0550ffff11..07a07a83e10f 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"Bild gesendet"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Screenshot wird gespeichert..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot wird in Arbeitsprofil gespeichert…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Screenshot wird im vertraulichen Profil gespeichert"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot gespeichert"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshot konnte nicht gespeichert werden"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Äußeres Display"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Neues Gerät koppeln"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klicken, um neues Gerät zu koppeln"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Voreinstellung konnte nicht aktualisiert werden"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Blockierung des Gerätemikrofons aufheben?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Blockierung der Gerätekamera aufheben?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Blockierung von Gerätekamera und Gerätemikrofon aufheben?"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 937ba70120e3..a1dd5f920a39 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"έστειλε μια εικόνα"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Αποθήκευση στιγμιότυπου οθόνης..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Αποθήκευση στιγμιότ. οθόνης στο προφίλ εργασίας…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Αποθήκευση στιγμιότυπου οθόνης σε ιδιωτικό"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Το στιγμιότυπο οθόνης αποθηκεύτηκε"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Μη δυνατή αποθήκευση του στιγμιότυπου οθόνης"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Εξωτερική οθόνη"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Σύζευξη νέας συσκευής"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Κάντε κλικ για σύζευξη νέας συσκευής"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Δεν ήταν δυνατή η ενημέρωση της προεπιλογής"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Κατάργηση αποκλεισμού μικροφώνου συσκευής;"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Κατάργηση αποκλεισμού κάμερας συσκευής;"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Κατάργηση αποκλεισμού κάμερας και μικροφώνου συσκευής;"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 137b243cbd9c..542d660c2f67 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"sent an image"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Saving screenshot to private"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External display"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Pair new device"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Click to pair new device"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Couldn\'t update preset"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Unblock device camera and microphone?"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 1f68f4632d95..4ac0efe0d853 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"sent an image"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Saving screenshot to private"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External Display"</string> @@ -371,6 +372,7 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Pair new device"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Click to pair new device"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Couldn\'t update preset"</string> + <string name="hearing_devices_preset_label" msgid="7878267405046232358">"Preset"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Unblock device camera and microphone?"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 137b243cbd9c..542d660c2f67 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"sent an image"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Saving screenshot to private"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External display"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Pair new device"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Click to pair new device"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Couldn\'t update preset"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Unblock device camera and microphone?"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 137b243cbd9c..542d660c2f67 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"sent an image"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Saving screenshot to private"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External display"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Pair new device"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Click to pair new device"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Couldn\'t update preset"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Unblock device camera and microphone?"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index eb705ae637e3..67c332eef5f0 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"sent an image"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Saving screenshot to private"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External Display"</string> @@ -371,6 +372,7 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Pair new device"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Click to pair new device"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Couldn\'t update preset"</string> + <string name="hearing_devices_preset_label" msgid="7878267405046232358">"Preset"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Unblock device microphone?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Unblock device camera?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Unblock device camera and microphone?"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index ea7748cf7285..c6d206b8a746 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"envió una imagen"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Guardando la captura de pantalla..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando cap. de pantalla en perfil de trabajo…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Guardando captura de pantalla en privado"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Se guardó la captura de pantalla"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"No se pudo guardar la captura de pantalla"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Pantalla externa"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Vincular dispositivo nuevo"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Haz clic para vincular un dispositivo nuevo"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"No se pudo actualizar el ajuste predeterminado"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"¿Quieres desbloquear el micrófono del dispositivo?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"¿Quieres desbloquear la cámara del dispositivo?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"¿Quieres desbloquear la cámara y el micrófono del dispositivo?"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 83650671a623..76effd15c665 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ha enviado una imagen"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Guardando captura..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando captura en el perfil de trabajo…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Guardando captura de pantalla en espacio privado"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Se ha guardado la captura de pantalla"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"No se ha podido guardar la captura de pantalla"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Pantalla externa"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Emparejar nuevo dispositivo"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Haz clic para emparejar un nuevo dispositivo"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"No se ha podido actualizar el preajuste"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"¿Desbloquear el micrófono del dispositivo?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"¿Desbloquear la cámara del dispositivo?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"¿Desbloquear la cámara y el micrófono del dispositivo?"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 2df568153775..3d15aab3e216 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"saatis kujutise"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Kuvatõmmise salvestamine ..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekraanipildi salvestamine tööprofiilile …"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Ekraanipildi salvestamine privaatseks"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekraanipilt salvestati"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekraanipilti ei õnnestunud salvestada"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Väline ekraan"</string> @@ -278,7 +279,7 @@ <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkesta ühendus"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveeri"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Lülita automaatselt homme uuesti sisse"</string> - <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funktsioonid, nagu Kiirjagamine ja Leia mu seade, kasutavad Bluetoothi"</string> + <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Sellised funktsioonid nagu Kiirjagamine ja Leia mu seade kasutavad Bluetoothi."</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth lülitub sisse homme hommikul"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Heli jagamine"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Heli jagamine"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uue seadme sidumine"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Uue seadme sidumiseks klõpsake"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Eelseadistust ei saanud värskendada"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Kas tühistada seadme mikrofoni blokeerimine?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Kas tühistada seadme kaamera blokeerimine?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Kas tühistada seadme kaamera ja mikrofoni blokeerimine?"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 1f1082116a5a..db0e8e96502f 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"erabiltzaileak irudi bat bidali du"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Pantaila-argazkia gordetzen…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pantaila-argazkia laneko profilean gordetzen…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Pantaila-argazkia profil pribatuan gordetzen"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Gorde da pantaila-argazkia"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Ezin izan da gorde pantaila-argazkia"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Kanpoko pantaila"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Parekatu beste gailu bat"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Egin klik beste gailu bat parekatzeko"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ezin izan da eguneratu aurrezarpena"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Gailuaren mikrofonoa desblokeatu nahi duzu?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Gailuaren kamera desblokeatu nahi duzu?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Gailuaren kamera eta mikrofonoa desblokeatu nahi dituzu?"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 94befe463b4a..f2372a6d61d5 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"تصویری ارسال کرد"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"درحال ذخیره نماگرفت…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"درحال ذخیره کردن نماگرفت در نمایه کاری…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"درحال ذخیره کردن نماگرفت در نمایه خصوصی"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"نماگرفت ذخیره شد"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"نماگرفت ذخیره نشد"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"نمایشگر خارجی"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"جفت کردن دستگاه جدید"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"برای جفت کردن دستگاه جدید، کلیک کنید"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"پیشتنظیم بهروزرسانی نشد"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"میکروفون دستگاه لغو انسداد شود؟"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"دوربین دستگاه لغو انسداد شود؟"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"دوربین و میکروفون دستگاه لغو انسداد شود؟"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 08c7adb042cc..9172a9acdbe4 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"lähetti kuvan"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Tallennetaan kuvakaappausta..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Kuvakaappausta tallennetaan työprofiiliin…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Tallennetaan kuvakaappausta yksityiseen profiiliin"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Kuvakaappaus tallennettu"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Kuvakaappauksen tallennus epäonnistui"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Ulkoinen näyttö"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Muodosta uusi laitepari"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Muodosta uusi laitepari klikkaamalla"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Esiasetusta ei voitu muuttaa"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Kumotaanko laitteen mikrofonin esto?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Kumotaanko laitteen kameran esto?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Kumotaanko laitteen kameran ja mikrofonin esto?"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 59b9e42e1ab6..f2bae1b1a488 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"a envoyé une image"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Enregistrement capture écran…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sauv. de la capture dans le profil prof. en cours…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Enregistrement de la capture d\'écran dans le profil privé en cours…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Écran externe"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Associer un nouvel appareil"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Cliquez ici pour associer un nouvel appareil"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Impossible de mettre à jour le préréglage"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Débloquer le microphone de l\'appareil?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Débloquer l\'appareil photo de l\'appareil?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Débloquer l\'appareil photo et le microphone?"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 79f70af71421..e216c8453b07 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"a envoyé une image"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Enregistrement de la capture d\'écran…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Enregistrement de capture d\'écran dans profil pro…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Enregistrement de la capture d\'écran dans le profil privé"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Écran externe"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Associer un nouvel appareil"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Cliquer pour associer un nouvel appareil"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Impossible de mettre à jour les préréglages"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Débloquer le micro de l\'appareil ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Débloquer la caméra de l\'appareil ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Débloquer l\'appareil photo et le micro de l\'appareil ?"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index fe2f1121f642..1ccab5d8dd60 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -36,7 +36,7 @@ <string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Xirar pantalla automaticamente"</string> <string name="usb_device_permission_prompt" msgid="4414719028369181772">"Queres permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Queres permitir que a aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> acceda ao dispositivo (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?\nEsta aplicación non está autorizada para realizar gravacións, pero podería capturar audio a través deste dispositivo USB."</string> - <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> + <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"Queres permitir que a aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> <string name="usb_audio_device_confirm_prompt_title" msgid="8828406516732985696">"Abrir <xliff:g id="APPLICATION">%1$s</xliff:g> para usar <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string> <string name="usb_audio_device_prompt_warn" msgid="2504972133361130335">"Esta aplicación non está autorizada a realizar gravacións, pero podería capturar audio a través deste dispositivo USB. Ao usar a aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> con este dispositivo, é posible que non se escoiten chamadas, notificacións nin alarmas."</string> <string name="usb_audio_device_prompt" msgid="7944987408206252949">"Ao usar a aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> con este dispositivo, é posible que non se escoiten chamadas, notificacións nin alarmas."</string> @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"enviou unha imaxe"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Gardando captura de pantalla…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gardando captura de pantalla no perfil de traballo"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Gardando captura de pantalla no perfil privado"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Gardouse a captura de pantalla"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Non se puido gardar a captura de pantalla"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Pantalla externa"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Vincular dispositivo novo"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Fai clic para vincular un novo dispositivo"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Non se puido actualizar a configuración predeterminada"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Queres desbloquear o micrófono do dispositivo?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Queres desbloquear a cámara do dispositivo?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Queres desbloquear a cámara e o micrófono do dispositivo?"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 299598f9decf..e096fff98221 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"છબી મોકલી"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"સ્ક્રીનશોટ સાચવી રહ્યું છે…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ઑફિસની પ્રોફાઇલમાં સ્ક્રીનશૉટ સાચવી રહ્યાં છીએ…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ખાનગી પ્રોફાઇલમાં સ્ક્રીનશૉટ સાચવી રહ્યાં છીએ"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"સ્ક્રીનશૉટ સાચવ્યો"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"સ્ક્રીનશૉટ સાચવી શક્યાં નથી"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"બાહ્ય ડિસ્પ્લે"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"નવા ડિવાઇસ સાથે જોડાણ કરો"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"નવા ડિવાઇસ સાથે જોડાણ કરવા માટે ક્લિક કરો"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"પ્રીસેટ અપડેટ કરી શક્યા નથી"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ડિવાઇસના માઇક્રોફોનને અનબ્લૉક કરીએ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ડિવાઇસના કૅમેરાને અનબ્લૉક કરીએ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ડિવાઇસના કૅમેરા અને માઇક્રોફોનને અનબ્લૉક કરીએ?"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 4c34814a502b..d8c7dcd67f18 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"एक इमेज भेजी गई"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"स्क्रीनशॉट सहेजा जा रहा है..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"स्क्रीनशॉट, वर्क प्रोफ़ाइल में सेव किया जा रहा है…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"स्क्रीनशॉट को निजी प्रोफ़ाइल में सेव किया जा रहा है"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव किया गया"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव नहीं किया जा सका"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"बाहरी डिसप्ले"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"नया डिवाइस जोड़ें"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"नया डिवाइस जोड़ने के लिए क्लिक करें"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"प्रीसेट अपडेट नहीं किया जा सका"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"क्या आपको डिवाइस का माइक्रोफ़ोन अनब्लॉक करना है?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"क्या आपको डिवाइस का कैमरा अनब्लॉक करना है?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"क्या आप डिवाइस का कैमरा और माइक्रोफ़ोन अनब्लॉक करना चाहते हैं?"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 5b5bddc5c1a7..19625bd0c887 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"šalje sliku"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Spremanje snimke zaslona..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Spremanje snimke zaslona na poslovni profil…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Spremanje snimke zaslona na osobni profil"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimka zaslona je spremljena"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Snimka zaslona nije spremljena"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Vanjski prikaz"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uparite novi uređaj"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknite da biste uparili novi uređaj"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ažuriranje unaprijed definiranih postavki nije uspjelo"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite li deblokirati mikrofon uređaja?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Želite li deblokirati kameru uređaja?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Želite li deblokirati kameru i mikrofon uređaja?"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index a2cf7b2002c5..70990336e93f 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"képet küldött"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Képernyőkép mentése..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Képernyőkép mentése a munkaprofilba…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Képernyőkép mentése a privát területre"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"A képernyőkép mentése sikerült"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Nem sikerült a képernyőkép mentése"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Külső kijelző"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Új eszköz párosítása"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kattintson új eszköz párosításához"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Nem sikerült frissíteni a beállításkészletet"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Feloldja az eszköz mikrofonjának letiltását?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Feloldja az eszköz kamerájának letiltását?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Feloldja az eszköz kamerájának és mikrofonjának letiltását?"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index b0f484807845..c539407f4efd 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"պատկեր է ուղարկվել"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Սքրինշոթը պահվում է..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Սքրինշոթը պահվում է աշխատանքային պրոֆիլում…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Սքրինշոթը պահվում է մասնավոր պրոֆիլում"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Սքրինշոթը պահվեց"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Չհաջողվեց պահել սքրինշոթը"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Արտաքին էկրան"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Նոր սարքի զուգակցում"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Սեղմեք՝ նոր սարք զուգակցելու համար"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Չհաջողվեց թարմացնել կարգավորումների հավաքածուն"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Արգելահանե՞լ սարքի խոսափողը"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Արգելահանե՞լ սարքի տեսախցիկը"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Արգելահանե՞լ սարքի տեսախցիկը և խոսափողը"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index a0795dcabe1d..5af06e8f6f11 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"mengirim gambar"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Menyimpan screenshot..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan screenshot ke profil kerja …"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Menyimpan screenshot ke profil privasi"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot disimpan"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan screenshot"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Layar Eksternal"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Sambungkan perangkat baru"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klik untuk menyambungkan perangkat baru"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Tidak dapat memperbarui preset"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Berhenti memblokir mikrofon perangkat?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Berhenti memblokir kamera perangkat?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Berhenti memblokir kamera dan mikrofon perangkat?"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index f32f6a6dfb06..ee99882d6b7c 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"sendi mynd"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Vistar skjámynd…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Vistar skjámynd á vinnusnið…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Vistar skjámynd í lokað"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skjámynd vistuð"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekki var hægt að vista skjámynd"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Ytri skjár"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Para nýtt tæki"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Smelltu til að para nýtt tæki"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Tókst ekki að uppfæra forstillingu"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Opna fyrir hljóðnema tækisins?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Opna fyrir myndavél tækisins?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Opna fyrir myndavél og hljóðnema tækisins?"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 1988459b140b..b42bd95f32e0 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -76,6 +76,8 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"è stata inviata un\'immagine"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Salvataggio screenshot…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvataggio screenshot nel profilo di lavoro…"</string> + <!-- no translation found for screenshot_saving_private_profile (8934706048497093297) --> + <skip /> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot salvato"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Impossibile salvare lo screenshot"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Display esterno"</string> @@ -371,6 +373,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Accoppia nuovo dispositivo"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Fai clic per accoppiare un nuovo dispositivo"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Impossibile aggiornare preset"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vuoi sbloccare il microfono del dispositivo?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vuoi sbloccare la fotocamera del dispositivo?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Vuoi sbloccare la fotocamera e il microfono del dispositivo?"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index d31a95d44df4..71d0c6be20b6 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"נשלחה תמונה"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"המערכת שומרת את צילום המסך..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"צילום המסך נשמר בפרופיל העבודה…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"צילום המסך נשמר בפרופיל האישי"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"צילום המסך נשמר"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"לא ניתן היה לשמור את צילום המסך"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"תצוגה במסך חיצוני"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"התאמה של מכשיר חדש"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"צריך ללחוץ כדי להתאים מכשיר חדש"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"לא ניתן לעדכן את ההגדרה הקבועה מראש"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"לבטל את חסימת המיקרופון של המכשיר?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"לבטל את חסימת המצלמה של המכשיר?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"לבטל את חסימת המצלמה והמיקרופון של המכשיר?"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index bff8487f1be3..d6dc93c8887f 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"画像を送信しました"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"スクリーンショットを保存しています..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"スクリーンショットを仕事用プロファイルに保存中…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"スクリーンショットをプライベートに保存しています"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"スクリーンショットを保存しました"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"スクリーンショット保存エラー"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"外側ディスプレイ"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"新しいデバイスとペア設定"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"クリックすると、新しいデバイスをペア設定できます"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"プリセットを更新できませんでした"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"デバイスのマイクのブロックを解除しますか?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"デバイスのカメラのブロックを解除しますか?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"デバイスのカメラとマイクのブロックを解除しますか?"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 51ba0801aa05..2670e4542344 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"გაიგზავნა სურათი"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ეკრანის სურათის შენახვა…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"მიმდინარეობს ეკრანის ანაბეჭდის შენახვა სამუშაო პროფილში…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"მიმდინარეობს ეკრანის ანაბეჭდის შენახვა კერძო სივრცეში"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ეკრანის ანაბეჭდი შენახულია"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"ეკრანის ანაბეჭდის შენახვა ვერ მოხერხდა"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"გარე ეკრანი"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ახალი მოწყობილობის დაწყვილება"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"დააწკაპუნეთ ახალი მოწყობილობის დასაწყვილებლად"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"წინასწარ დაყენებული პარამეტრების განახლება ვერ მოხერხდა"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"გსურთ მოწყობილობის მიკროფონის განბლოკვა?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"გსურთ მოწყობილობის კამერის განბლოკვა?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"გსურთ მოწყობილობის კამერის და მიკროფონის განბლოკვა?"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 27c8512c160d..c709bbec2b1f 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"сурет жіберілді"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Скриншотты сақтауда…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жұмыс профиліне сақталып жатыр…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Скриншот жеке профильде сақталып жатыр."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сақталды"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сақталмады"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Сыртқы дисплей"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Жаңа құрылғыны жұптау"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Жаңа құрылғыны жұптау үшін басыңыз."</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Параметрлер жинағын жаңарту мүмкін болмады."</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Құрылғы микрофонын блоктан шығару керек пе?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Құрылғы камерасын блоктан шығару керек пе?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Құрылғы камерасы мен микрофонын блоктан шығару керек пе?"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index bd0bf9896c16..042e7481370c 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"បានផ្ញើរូបភាព"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"កំពុងរក្សាទុករូបថតអេក្រង់..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"កំពុងរក្សាទុករូបថតអេក្រង់ទៅកម្រងព័ត៌មានការងារ…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"កំពុងរក្សាទុករូបថតអេក្រង់ទៅក្នុងកម្រងព័ត៌មានឯកជន"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"បានរក្សាទុករូបថតអេក្រង់"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"មិនអាចរក្សាទុករូបថតអេក្រង់បានទេ"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ផ្ទាំងអេក្រង់ខាងក្រៅ"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ផ្គូផ្គងឧបករណ៍ថ្មី"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ចុច ដើម្បីផ្គូផ្គងឧបករណ៍ថ្មី"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"មិនអាចប្ដូរការកំណត់ជាមុនបានទេ"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ឈប់ទប់ស្កាត់មីក្រូហ្វូនរបស់ឧបករណ៍ឬ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ឈប់ទប់ស្កាត់កាមេរ៉ារបស់ឧបករណ៍ឬ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ឈប់ទប់ស្កាត់កាមេរ៉ា និងមីក្រូហ្វូនរបស់ឧបករណ៍ឬ?"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index bc7ab504d597..8b1ab5f3b7e3 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ಚಿತ್ರವನ್ನು ಕಳುಹಿಸಲಾಗಿದೆ"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ಗೆ ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಖಾಸಗಿ ಪ್ರೊಫೈಲ್ಗೆ ಸೇವ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ಬಾಹ್ಯ ಡಿಸ್ಪ್ಲೇ"</string> @@ -279,7 +280,7 @@ <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ಸಕ್ರಿಯಗೊಳಿಸಿ"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ನಾಳೆ ಪುನಃ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಆನ್ ಮಾಡಿ"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"ಕ್ವಿಕ್ ಶೇರ್ ಮತ್ತು Find My Device ನಂತಹ ಫೀಚರ್ಗಳು ಬ್ಲೂಟೂತ್ ಅನ್ನು ಬಳಸುತ್ತವೆ"</string> - <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ಬ್ಲೂಟೂತ್ ನಾಳೆ ಬೆಳಿಗ್ಗೆ ಆನ್ ಆಗುತ್ತದೆ"</string> + <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"ಬ್ಲೂಟೂತ್ ನಾಳೆ ಬೆಳಗ್ಗೆ ಆನ್ ಆಗುತ್ತದೆ"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"ಆಡಿಯೋ ಹಂಚಿಕೊಳ್ಳುವಿಕೆ"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"ಆಡಿಯೋವನ್ನು ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತಿದೆ"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ಹೊಸ ಸಾಧನವನ್ನು ಪೇರ್ ಮಾಡಿ"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ಪ್ರಿಸೆಟ್ ಅನ್ನು ಅಪ್ಡೇಟ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ಸಾಧನದ ಮೈಕ್ರೋಫೋನ್ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆಯಬೇಕೆ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ಸಾಧನದ ಕ್ಯಾಮರಾ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆಯಬೇಕೆ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ಸಾಧನದ ಕ್ಯಾಮರಾ ಮತ್ತು ಮೈಕ್ರೋಫೋನ್ ಅನ್ನು ಅನ್ಬ್ಲಾಕ್ ಮಾಡಬೇಕೇ?"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 0e6f9bfcbb41..8d34d00d5c11 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"이미지 보냄"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"캡쳐화면 저장 중..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"직장 프로필에 스크린샷 저장 중…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"스크린샷을 비공개 프로필에 저장 중"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"스크린샷 저장됨"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"스크린샷을 저장할 수 없음"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"외부 디스플레이"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"새 기기와 페어링"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"새 기기와 페어링하려면 클릭하세요"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"사전 설정을 업데이트할 수 없음"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"기기 마이크를 차단 해제하시겠습니까?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"기기 카메라를 차단 해제하시겠습니까?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"기기 카메라 및 마이크를 차단 해제하시겠습니까?"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 383a8e1a1052..975faf128eed 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"сүрөт жөнөттү"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Скриншот сакталууда..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жумуш профилине сакталууда…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Скриншот жеке профилге сакталууда"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сакталды"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сакталган жок"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Тышкы экран"</string> @@ -278,7 +279,7 @@ <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажыратуу"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"иштетүү"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Эртең автоматтык түрдө кайра күйгүзүү"</string> - <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Тез бөлүшүү жана Түзмөгүм кайда? сыяктуу функциялар Bluetooth\'ту колдонушат"</string> + <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Bluetooth Тез бөлүшүү жана Түзмөгүм кайда? сыяктуу функцияларда колдонулат"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth эртең таңда күйөт"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Чогуу угуу"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Чогуу угулууда"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Жаңы түзмөк кошуу"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Жаңы түзмөк кошуу үчүн басыңыз"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Алдын ала коюлган параметрлер жаңыртылган жок"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Түзмөктүн микрофонун бөгөттөн чыгарасызбы?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Түзмөктүн камерасын бөгөттөн чыгарасызбы?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Түзмөктүн камерасы менен микрофону бөгөттөн чыгарылсынбы?"</string> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 56ebc0668097..aea79e84f7e8 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -28,9 +28,7 @@ <!-- In landscape the security footer is actually part of the header, and needs to be as short as the header --> - <dimen name="qs_security_footer_single_line_height">@*android:dimen/quick_qs_offset_height</dimen> <dimen name="qs_footer_padding">14dp</dimen> - <dimen name="qs_security_footer_background_inset">12dp</dimen> <dimen name="volume_tool_tip_top_margin">12dp</dimen> <dimen name="volume_row_slider_height">128dp</dimen> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index a136976df17d..22a64f86f26f 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ສົ່ງຮູບແລ້ວ"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ກຳລັງບັນທຶກພາບໜ້າຈໍ..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ກຳລັງບັນທຶກຮູບໜ້າຈໍໃສ່ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ກຳລັງບັນທຶກຮູບໜ້າຈໍໄວ້ໃນໂປຣໄຟລ໌ສ່ວນຕົວ"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ບັນທຶກຮູບໜ້າຈໍໄວ້ແລ້ວ"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ຈໍສະແດງຜົນພາຍນອກ"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ຈັບຄູ່ອຸປະກອນໃໝ່"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ຄລິກເພື່ອຈັບຄູ່ອຸປະກອນໃໝ່"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ບໍ່ສາມາດອັບເດດການຕັ້ງຄ່າລ່ວງໜ້າໄດ້"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ປົດບລັອກໄມໂຄຣໂຟນອຸປະກອນບໍ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ປົດບລັອກກ້ອງຖ່າຍຮູບອຸປະກອນບໍ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ຍົກເລີກການບລັອກກ້ອງຖ່າຍຮູບ ຫຼື ໄມໂຄຣໂຟນອຸປະກອນບໍ?"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 96be8e2093dd..bec3cc1e61f2 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"išsiuntė vaizdą"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Išsaugoma ekrano kopija..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Išsaugoma ekrano kopija darbo profilyje…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Ekrano kopija išsaugoma privačiame profilyje"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrano kopija išsaugota"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrano kopijos išsaugoti nepavyko"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Išorinė pateiktis"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Susieti naują įrenginį"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Spustelėkite, kad susietumėte naują įrenginį"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Išankstinių nustatymų atnaujinti nepavyko"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Panaikinti įrenginio mikrofono blokavimą?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Panaikinti įrenginio fotoaparato blokavimą?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Panaikinti įrenginio fotoaparato ir mikrofono blokavimą?"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index b8a54765c006..001f6a027ce4 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"nosūtīts attēls"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Notiek ekrānuzņēmuma saglabāšana..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Notiek ekrānuzņēmuma saglabāšana darba profilā…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Ekrānuzņēmums tiek saglabāts privātajā profilā"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrānuzņēmums saglabāts"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrānuzņēmumu neizdevās saglabāt."</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Ārējais displejs"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Savienot pārī jaunu ierīci"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Noklikšķiniet, lai savienotu pārī jaunu ierīci"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Nevarēja atjaunināt pirmsiestatījumu"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vai atbloķēt ierīces mikrofonu?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vai vēlaties atbloķēt ierīces kameru?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Vai atbloķēt ierīces kameru un mikrofonu?"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 2e0edbeed9f5..12bf5689525b 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"испрати слика"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Сликата на екранот се зачувува..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Се зачувува слика од екранот на вашиот работен профил…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Сликата од екранот се зачувува во приватниот профил"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Сликата од екранот е зачувана"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Не може да се зачува слика од екранот"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Надворешен екран"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Спари нов уред"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Кликнете за да спарите нов уред"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Не можеше да се ажурира зададената вредност"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Да се одблокира пристапот до микрофонот на уредот?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Да се одблокира пристапот до камерата на уредот?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Да се одблокира пристапот до камерата и микрофонот на уредот?"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index ad7d723741be..d165242e49e1 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -26,7 +26,7 @@ <string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> ശേഷിക്കുന്നു"</string> <string name="invalid_charger_title" msgid="938685362320735167">"USB വഴി ചാർജ് ചെയ്യാനാകില്ല"</string> <string name="invalid_charger_text" msgid="2339310107232691577">"ഉപകരണത്തിനൊപ്പം ലഭിച്ച ചാർജർ ഉപയോഗിക്കുക"</string> - <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"ബാറ്ററി ലാഭിക്കൽ ഓണാക്കണോ?"</string> + <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"ബാറ്ററി സേവർ ഓണാക്കണോ?"</string> <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"ബാറ്ററി ലാഭിക്കലിനെ കുറിച്ച്"</string> <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ഓൺ ചെയ്യുക"</string> <string name="battery_saver_start_action" msgid="8353766979886287140">"ഓണാക്കുക"</string> @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ചിത്രം അയച്ചു"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നു..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നു…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"സ്ക്രീൻഷോട്ട് സ്വകാര്യമാക്കി സംരക്ഷിക്കുന്നു"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"സ്ക്രീൻഷോട്ട് സംരക്ഷിച്ചു"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കാനായില്ല"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ബാഹ്യ ഡിസ്പ്ലേ"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"പുതിയ ഉപകരണം ജോടിയാക്കുക"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"പുതിയ ഉപകരണം ജോടിയാക്കാൻ ക്ലിക്ക് ചെയ്യുക"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"പ്രീസെറ്റ് അപ്ഡേറ്റ് ചെയ്യാനായില്ല"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ഉപകരണ മൈക്രോഫോൺ അൺബ്ലോക്ക് ചെയ്യണോ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ഉപകരണ ക്യാമറ അൺബ്ലോക്ക് ചെയ്യണോ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ഉപകരണ ക്യാമറയോ മൈക്രോഫോണോ അൺബ്ലോക്ക് ചെയ്യണോ?"</string> @@ -720,7 +723,7 @@ <string name="snoozed_for_time" msgid="7586689374860469469">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> സമയത്തേക്ക് സ്നൂസ് ചെയ്തു"</string> <string name="snoozeHourOptions" msgid="2332819756222425558">"{count,plural, =1{# മണിക്കൂർ}=2{# മണിക്കൂർ}other{# മണിക്കൂർ}}"</string> <string name="snoozeMinuteOptions" msgid="2222082405822030979">"{count,plural, =1{# മിനിറ്റ്}other{# മിനിറ്റ്}}"</string> - <string name="battery_detail_switch_title" msgid="6940976502957380405">"ബാറ്ററി ലാഭിക്കൽ"</string> + <string name="battery_detail_switch_title" msgid="6940976502957380405">"ബാറ്ററി സേവർ"</string> <string name="keyboard_key_button_template" msgid="8005673627272051429">"ബട്ടൺ <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="keyboard_key_home" msgid="3734400625170020657">"ഹോം"</string> <string name="keyboard_key_back" msgid="4185420465469481999">"ബാക്ക്"</string> @@ -943,7 +946,7 @@ <string name="slice_permission_checkbox" msgid="4242888137592298523">"ഏത് ആപ്പിൽ നിന്നും സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP">%1$s</xliff:g>-നെ അനുവദിക്കുക"</string> <string name="slice_permission_allow" msgid="6340449521277951123">"അനുവദിക്കുക"</string> <string name="slice_permission_deny" msgid="6870256451658176895">"നിരസിക്കുക"</string> - <string name="auto_saver_title" msgid="6873691178754086596">"ബാറ്ററി ലാഭിക്കൽ ഷെഡ്യൂൾ ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> + <string name="auto_saver_title" msgid="6873691178754086596">"ബാറ്ററി സേവർ ഷെഡ്യൂൾ ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> <string name="auto_saver_text" msgid="3214960308353838764">"ബാറ്ററി ചാർജ് തീരാൻ സാധ്യതയുണ്ടെങ്കിൽ ഓണാക്കുക"</string> <string name="no_auto_saver_action" msgid="7467924389609773835">"വേണ്ട"</string> <string name="ongoing_privacy_dialog_a11y_title" msgid="2205794093673327974">"ഉപയോഗത്തിലാണ്"</string> @@ -1017,7 +1020,7 @@ <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"എഡ്ജിലേക്ക് നീക്കി മറയ്ക്കുക"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"എഡ്ജിൽ നിന്ന് നീക്കി കാണിക്കൂ"</string> <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"നീക്കം ചെയ്യുക"</string> - <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"മാറ്റുക"</string> + <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ടോഗിൾ ചെയ്യുക"</string> <string name="accessibility_floating_button_action_edit" msgid="1688227814600463987">"എഡിറ്റ് ചെയ്യുക"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ഉപകരണ നിയന്ത്രണങ്ങൾ"</string> <string name="controls_providers_title" msgid="6879775889857085056">"നിയന്ത്രണങ്ങൾ ചേർക്കാൻ ആപ്പ് തിരഞ്ഞെടുക്കുക"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index fb3531f739b3..2a0b70a30253 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"зураг илгээсэн"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Дэлгэцийн агшинг хадгалж байна…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Дэлгэцийн агшныг ажлын профайлд хадгалж байна…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Дэлгэцийн агшныг хаалттай профайлд хадгалж байна"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Дэлгэцээс дарсан зургийг хадгалсан"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Дэлгэцээс дарсан зургийг хадгалж чадсангүй"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Гадаад дэлгэц"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Шинэ төхөөрөмж хослуулах"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Шинэ төхөөрөмж хослуулахын тулд товшино уу"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Урьдчилсан тохируулгыг шинэчилж чадсангүй"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Төхөөрөмжийн микрофоныг блокоос гаргах уу?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Төхөөрөмжийн камерыг блокоос гаргах уу?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Төхөөрөмжийн камер болон микрофоныг блокоос гаргах уу?"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 249e7ac49b97..017a6c8067a5 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"इमेज पाठवली आहे"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"स्क्रीनशॉट सेव्ह करत आहे…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलवर स्क्रीनशॉट सेव्ह करत आहे…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"स्क्रीनशॉट खाजगीमध्ये सेव्ह करत आहे"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव्ह केला"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव्ह करू शकलो नाही"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"बाह्य डिस्प्ले"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"नवीन डिव्हाइस पेअर करा"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"नवीन डिव्हाइस पेअर करण्यासाठी क्लिक करा"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"प्रीसेट अपडेट करता आले नाही"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"डिव्हाइसचा मायक्रोफोन अनब्लॉक करायचा आहे का?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"डिव्हाइसचा कॅमेरा अनब्लॉक करायचा आहे का?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"डिव्हाइसचा कॅमेरा आणि मायक्रोफोन अनब्लॉक करायचा आहे का?"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index e21daa24ebb2..23e4559d97a0 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"menghantar imej"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Menyimpan tangkapan skrin..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan tangkapan skrin ke profil kerja…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Menyimpan tangkapan skrin pada profil peribadi"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Tangkapan skrin disimpan"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan tangkapan skrin"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Paparan Luaran"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Gandingkan peranti baharu"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klik untuk menggandingkan peranti baharu"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Tidak dapat mengemaskinikan pratetapan"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Nyahsekat mikrofon peranti?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Nyahsekat kamera peranti?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Nyahsekat kamera dan mikrofon peranti?"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 000d7d36533c..c3c42052d689 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ပုံပို့ထားသည်"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ဖန်သားပြင်ဓါတ်ပုံရိုက်ခြင်းအား သိမ်းဆည်းပါမည်"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"အလုပ်ပရိုဖိုင်တွင် ဖန်သားပြင်ဓာတ်ပုံ သိမ်းနေသည်…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ဖန်သားပြင်ဓာတ်ပုံကို သီးသန့်ပရိုဖိုင်တွင် သိမ်းနေသည်"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်းပြီးပါပြီ"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"မျက်နှာပြင်ပုံကို သိမ်း၍မရပါ"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ပြင်ပဖန်သားပြင်"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"စက်အသစ်တွဲချိတ်ရန်"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"စက်အသစ် တွဲချိတ်ရန် နှိပ်ပါ"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"အသင့်သုံးကို အပ်ဒိတ်လုပ်၍မရပါ"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"စက်၏မိုက်ခရိုဖုန်းကို ပြန်ဖွင့်မလား။"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"စက်၏ကင်မရာကို ပြန်ဖွင့်မလား။"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"စက်၏ကင်မရာနှင့် မိုက်ခရိုဖုန်းကို ပြန်ဖွင့်မလား။"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 3fdb8f129c2d..dbcfaa366656 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"har sendt et bilde"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Lagrer skjermdumpen …"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Lagrer skjermdumpen i jobbprofilen …"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Lagrer skjermdump i den private profilen"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skjermdumpen er lagret"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Kunne ikke lagre skjermdump"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Ekstern skjerm"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Koble til en ny enhet"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klikk for å koble til en ny enhet"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Kunne ikke oppdatere forhåndsinnstillingen"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vil du oppheve blokkeringen av enhetsmikrofonen?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vil du oppheve blokkeringen av enhetskameraet?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Vil du oppheve blokkeringen av enhetskameraet og -mikrofonen?"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index fe4b4129e2dc..aa7e3140b635 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"कुनै छवि पठाइयो"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"स्क्रिनसट बचत गर्दै…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलमा स्क्रिनसट सेभ गरिँदै छ…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"निजी प्रोफाइलमा स्क्रिनसट सेभ गरिँदै छ"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रिनसट सेभ गरियो"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"बाह्य डिस्प्ले"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"नयाँ डिभाइस कनेक्ट गर्नुहोस्"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"नयाँ डिभाइसमा कनेक्ट गर्न क्लिक गर्नुहोस्"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"प्रिसेट अपडेट गर्न सकिएन"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"डिभाइसको माइक्रोफोन अनब्लक गर्ने हो?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"डिभाइसको क्यामेरा अनब्लक गर्ने हो?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"डिभाइसको क्यामेरा र माइक्रोफोन अनब्लक गर्ने हो?"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index add74db5e587..3b460e51f7a5 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"heeft een afbeelding gestuurd"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Screenshot opslaan..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot opslaan in werkprofiel…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Screenshot opslaan in privéprofiel"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot opgeslagen"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Kan screenshot niet opslaan"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Extern scherm"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Nieuw apparaat koppelen"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klik om nieuw apparaat te koppelen"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Kan voorinstelling niet updaten"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Microfoon van apparaat niet meer blokkeren?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Apparaatcamera niet meer blokkeren?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Blokkeren van apparaatcamera en -microfoon opheffen?"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 6a93557ee4a1..29a5e7302530 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ଏକ ଛବି ପଠାଯାଇଛି"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ କରାଯାଉଛି…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ୱାର୍କ ପ୍ରୋଫାଇଲରେ ସ୍କ୍ରିନସଟ ସେଭ କରାଯାଉଛି…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ପ୍ରାଇଭେଟରେ ସ୍କ୍ରିନସଟକୁ ସେଭ କରାଯାଉଛି"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ ହୋଇଛି"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"ସ୍କ୍ରୀନ୍ଶଟ୍ ସେଭ୍ କରିହେବ ନାହିଁ"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେ"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ନୂଆ ଡିଭାଇସ ପେୟାର କରନ୍ତୁ"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ନୂଆ ଡିଭାଇସ ପେୟାର କରିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ପ୍ରିସେଟକୁ ଅପଡେଟ କରାଯାଇପାରିଲା ନାହିଁ"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ଡିଭାଇସର ମାଇକ୍ରୋଫୋନକୁ ଅନବ୍ଲକ କରିବେ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ଡିଭାଇସର କେମେରାକୁ ଅନବ୍ଲକ କରିବେ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ଡିଭାଇସର କ୍ୟାମେରା ଏବଂ ମାଇକ୍ରୋଫୋନକୁ ଅନବ୍ଲକ୍ କରିବେ?"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index c9086187cb22..624557597677 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ਚਿੱਤਰ ਭੇਜਿਆ ਗਿਆ"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਸੁਰੱਖਿਅਤ ਕਰ ਰਿਹਾ ਹੈ…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਪ੍ਰਾਈਵੇਟ ਵਜੋਂ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ਬਾਹਰੀ ਡਿਸਪਲੇ"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ਨਵਾਂ ਡੀਵਾਈਸ ਜੋੜਾਬੱਧ ਕਰੋ"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"\'ਨਵਾਂ ਡੀਵਾਈਸ ਜੋੜਾਬੱਧ ਕਰੋ\' \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ਪ੍ਰੀਸੈੱਟ ਨੂੰ ਅੱਪਡੇਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ਕੀ ਡੀਵਾਈਸ ਦੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਅਣਬਲਾਕ ਕਰਨਾ ਹੈ?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ਕੀ ਡੀਵਾਈਸ ਦੇ ਕੈਮਰੇ ਨੂੰ ਅਣਬਲਾਕ ਕਰਨਾ ਹੈ?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"ਕੀ ਡੀਵਾਈਸ ਦੇ ਕੈਮਰੇ ਅਤੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਅਣਬਲਾਕ ਕਰਨਾ ਹੈ?"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index a13a1d61dc7c..e8e6f09d0d41 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"wysłano obraz"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Zapisywanie zrzutu ekranu..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Zapisuję zrzut ekranu w profilu służbowym…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Zapisuję zrzut ekranu w profilu prywatnym"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Zrzut ekranu został zapisany"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Nie udało się zapisać zrzutu ekranu"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Wyświetlacz zewnętrzny"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Sparuj nowe urządzenie"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknij, aby sparować nowe urządzenie"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Nie udało się zaktualizować gotowego ustawienia"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Odblokować mikrofon urządzenia?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Odblokować aparat urządzenia?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Odblokować aparat i mikrofon urządzenia?"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index da72fd51eecf..e72b45a4742d 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"enviou uma imagem"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Salvando captura de tela..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Salvando captura de tela no perfil particular"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Tela externa"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Parear novo dispositivo"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Clique para parear o novo dispositivo"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Não foi possível atualizar a predefinição"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Desbloquear o microfone do dispositivo?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Desbloquear a câmera do dispositivo?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Desbloquear a câmera e o microfone do dispositivo?"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 1d9a8da1499f..1912b7ef44d3 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"enviou uma imagem"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"A guardar captura de ecrã..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"A guardar captura de ecrã no perfil de trabalho…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"A guardar a captura de ecrã em privado"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de ecrã guardada"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Não foi possível guardar a captura de ecrã"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Ecrã externo"</string> @@ -278,7 +279,7 @@ <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desassociar"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Reativar amanhã automaticamente"</string> - <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"As funcionalidades como a Partilha rápida e o serviço Localizar o meu dispositivo usam o Bluetooth"</string> + <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funcionalidades como a Partilha rápida e o serviço Localizar o meu dispositivo usam o Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"O Bluetooth vai ser ativado amanhã de manhã"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Partilha de áudio"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"A partilhar áudio"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Sincronizar novo dispositivo"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Clique para sincronizar um novo dispositivo"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Não foi possível atualizar a predefinição"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Desbloquear o microfone do dispositivo?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Desbloquear a câmara do dispositivo?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Quer desbloquear a câmara e o microfone?"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index da72fd51eecf..e72b45a4742d 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"enviou uma imagem"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Salvando captura de tela..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Salvando captura de tela no perfil particular"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Tela externa"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Parear novo dispositivo"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Clique para parear o novo dispositivo"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Não foi possível atualizar a predefinição"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Desbloquear o microfone do dispositivo?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Desbloquear a câmera do dispositivo?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Desbloquear a câmera e o microfone do dispositivo?"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index f0f5987d4220..b6614c671126 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"a trimis o imagine"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Se salvează captura de ecran..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Se salvează captura în profilul de serviciu…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Se salvează captura de ecran în profilul privat"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Captură de ecran salvată"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Nu s-a putut salva captura de ecran"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Afișaj extern"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Asociază un nou dispozitiv"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Dă clic pentru a asocia un nou dispozitiv"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Nu s-a putut actualiza presetarea"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Deblochezi microfonul dispozitivului?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Deblochezi camera dispozitivului?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Deblochezi camera și microfonul dispozitivului?"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index bb79bc2a7140..d6fa6abcbdc6 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"отправлено изображение"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Сохранение..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Сохранение скриншота в рабочем профиле…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Сохранение скриншота в частный профиль…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сохранен"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Не удалось сохранить скриншот"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Внешний дисплей"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Подключить новое устройство"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Нажмите, чтобы подключить новое устройство"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Не удалось обновить набор настроек."</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Разблокировать микрофон устройства?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Разблокировать камеру устройства?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Разблокировать камеру и микрофон устройства?"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index ad801c29607a..3de42252dfee 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"රූපයක් එවන ලදී"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"තිර රුව සුරැකෙමින් පවතී…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"කාර්යාල පැතිකඩ වෙත තිර රුව සුරකිමින්…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"තිර රුව පුද්ගලික ලෙස සුරැකේ"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"තිර රුව සුරකින ලදී"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"තිර රුව සුරැකිය නොහැකි විය"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"බාහිර සංදර්ශකය"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"නව උපාංගය යුගල කරන්න"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"නව උපාංගය යුගල කිරීමට ක්ලික් කරන්න"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"පෙර සැකසීම යාවත්කාලීන කළ නොහැකි විය"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"උපාංග මයික්රෆෝනය අවහිර කිරීම ඉවත් කරන්නද?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"උපාංග කැමරාව අවහිර කිරීම ඉවත් කරන්නද?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"උපාංග කැමරාව සහ මයික්රෆෝනය අවහිර කිරීම ඉවත් කරන්නද?"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 5631ab03a893..cb8cf5beb172 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"odoslal(a) obrázok"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Prebieha ukladanie snímky obrazovky..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukladá sa snímka obrazovky do pracovného profilu…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Snímka obrazovky sa ukladá do súkromného profilu"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snímka obrazovky bola uložená"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Snímku obrazovky sa nepodarilo uložiť"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Externá obrazovka"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Spárovať nové zariadenie"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknutím spárujete nové zariadenie"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Predvoľbu sa nepodarilo aktualizovať"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Chcete odblokovať mikrofón zariadenia?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Chcete odblokovať kameru zariadenia?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Chcete odblokovať fotoaparát a mikrofón zariadenia?"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index a6b31c95d20c..a3e4487898fd 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"je poslal(-a) sliko"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Shranjevanje posnetka zaslona ..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Shranjevanje posnetka zaslona v delovni profil …"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Shranjevanje posnetka zaslona v zasebni profil"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Posnetek zaslona je shranjen"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Posnetka zaslona ni bilo mogoče shraniti"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Zunanji zaslon"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Seznanitev nove naprave"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknite za seznanitev nove naprave"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Prednastavljenih vrednosti ni bilo mogoče posodobiti"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite odblokirati mikrofon v napravi?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Želite odblokirati fotoaparat v napravi?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Želite odblokirati fotoaparat in mikrofon v napravi?"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index acbf23416f53..b06890d92e3d 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -76,6 +76,8 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"dërgoi një imazh"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Po ruan pamjen e ekranit…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pamja e ekranit po ruhet te profili i punës…"</string> + <!-- no translation found for screenshot_saving_private_profile (8934706048497093297) --> + <skip /> <string name="screenshot_saved_title" msgid="8893267638659083153">"Pamja e ekranit u ruajt"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Pamja e ekranit nuk mund të ruhej"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Ekrani i jashtëm"</string> @@ -371,6 +373,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Çifto pajisje të re"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliko për të çiftuar një pajisje të re"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Paravendosja nuk mund të përditësohej"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Të zhbllokohet mikrofoni i pajisjes?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Të zhbllokohet kamera e pajisjes?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Të zhbllokohen kamera dhe mikrofoni i pajisjes?"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index d21536bc9368..2b851b4b522b 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"је послао/ла слику"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Чување снимка екрана..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Снимак екрана се чува на пословном профилу…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Снимак екрана се чува на приватном профилу"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Снимак екрана је сачуван"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Чување снимка екрана није успело"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Спољни екран"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Упари нови уређај"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Кликните да бисте упарили нов уређај"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ажурирање задатих подешавања није успело"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Желите да одблокирате микрофон уређаја?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Желите да одблокирате камеру уређаја?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Желите да одблокирате камеру и микрофон уређаја?"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 95c5e15f1dc3..0d1614fa66bd 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"har skickat en bild"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Skärmbilden sparas ..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sparar skärmbild i jobbprofilen …"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Sparar skärmbilden till privat profil"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skärmbilden har sparats"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Det gick inte att spara skärmbilden"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Extern skärm"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Parkoppla en ny enhet"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klicka för att parkoppla en ny enhet"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Det gick inte att uppdatera förinställningen"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vill du återaktivera enhetens mikrofon?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vill du återaktivera enhetens kamera?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Vill du återaktivera enhetens kamera och mikrofon?"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index fab95f0df4fc..670a85c181f5 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"imetuma picha"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Inahifadhi picha ya skrini..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Inahifadhi picha ya skrini kwenye wasifu wa kazini…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Inahifadhi picha ya skrini kwenye wasifu wa faragha"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Imehifadhi picha ya skrini"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Imeshindwa kuhifadhi picha ya skrini"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Skrini ya Nje"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Unganisha kifaa kipya"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Bofya ili uunganishe kifaa kipya"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Imeshindwa kusasisha mipangilio iliyowekwa mapema"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Ungependa kuwacha kuzuia maikrofoni ya kifaa?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Ungependa kuacha kuzuia kamera ya kifaa?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Ungependa kuwacha kuzuia kamera na maikrofoni ya kifaa?"</string> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 2cfba01fe1c8..29e0dbea24f2 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -68,11 +68,6 @@ <dimen name="qs_brightness_margin_bottom">16dp</dimen> - <!-- For large screens the security footer appears below the footer, - same as phones in portrait --> - <dimen name="qs_security_footer_single_line_height">48dp</dimen> - <dimen name="qs_security_footer_background_inset">0dp</dimen> - <dimen name="qs_panel_padding_top">8dp</dimen> <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) --> @@ -102,6 +97,17 @@ <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> <dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen> + <!-- Dimensions for biometric prompt panel padding --> + <dimen name="biometric_prompt_one_pane_medium_top_guideline_padding">56dp</dimen> + <dimen name="biometric_prompt_one_pane_medium_horizontal_guideline_padding">@dimen/biometric_dialog_border_padding</dimen> + + <!-- Dimensions for biometric prompt scroll view padding --> + <dimen name="biometric_prompt_top_scroll_view_bottom_padding">32dp</dimen> + <dimen name="biometric_prompt_top_scroll_view_horizontal_padding">32dp</dimen> + + <!-- Dimensions for biometric prompt custom content view. --> + <dimen name="biometric_prompt_logo_description_top_padding">16dp</dimen> + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> <dimen name="biometric_auth_pattern_view_size">348dp</dimen> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index d8e6892a8a86..224ed3989819 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"படம் அனுப்பப்பட்டது"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ஸ்க்ரீன் ஷாட்டைச் சேமிக்கிறது…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"பணிக் கணக்கில் ஸ்கிரீன்ஷாட் சேமிக்கப்படுகிறது…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"தனிப்பட்ட சுயவிவரத்தில் ஸ்கிரீன்ஷாட் சேமிக்கப்படுகிறது"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ஸ்கிரீன்ஷாட் சேமிக்கப்பட்டது"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"ஸ்கிரீன் ஷாட்டைச் சேமிக்க முடியவில்லை"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"வெளித் திரை"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"புதிய சாதனத்தை இணை"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"புதிய சாதனத்தை இணைக்க கிளிக் செய்யலாம்"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"முன்னமைவைப் புதுப்பிக்க முடியவில்லை"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"சாதனத்தின் மைக்ரோஃபோனுக்கான தடுப்பை நீக்கவா?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"சாதனத்தின் கேமராவுக்கான தடுப்பை நீக்கவா?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"சாதனத்தின் கேமராவுக்கும் மைக்ரோஃபோனுக்குமான தடுப்பை நீக்கவா?"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 77c89c0857d2..26bb871c7deb 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ఇమేజ్ను పంపారు"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"స్క్రీన్షాట్ను సేవ్ చేస్తోంది…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"స్క్రీన్షాట్ను వర్క్ ప్రొఫైల్కు సేవ్ చేస్తోంది…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"స్క్రీన్షాట్ను ప్రైవేట్ ప్రొఫైల్కు సేవ్ చేస్తోంది"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"స్క్రీన్షాట్ సేవ్ చేయబడింది"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"స్క్రీన్షాట్ని సేవ్ చేయడం సాధ్యం కాలేదు"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"వెలుపలి డిస్ప్లే"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"కొత్త పరికరాన్ని పెయిర్ చేయండి"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"కొత్త పరికరాన్ని పెయిర్ చేయడానికి క్లిక్ చేయండి"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ప్రీసెట్ను అప్డేట్ చేయడం సాధ్యపడలేదు"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"పరికరం మైక్రోఫోన్ను అన్బ్లాక్ చేయమంటారా?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"పరికరంలోని కెమెరాను అన్బ్లాక్ చేయమంటారా?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"పరికరంలోని కెమెరా, మైక్రోఫోన్లను అన్బ్లాక్ చేయమంటారా?"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 0800a264cbb2..02ee0175f0e7 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ส่งรูปภาพ"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"กำลังบันทึกภาพหน้าจอ..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"กำลังบันทึกภาพหน้าจอไปยังโปรไฟล์งาน…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"กำลังบันทึกภาพหน้าจอลงในโปรไฟล์ส่วนตัว"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"บันทึกภาพหน้าจอแล้ว"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"บันทึกภาพหน้าจอไม่ได้"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"จอแสดงผลภายนอก"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"จับคู่อุปกรณ์ใหม่"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"คลิกเพื่อจับคู่อุปกรณ์ใหม่"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ไม่สามารถอัปเดตค่าที่กำหนดล่วงหน้า"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"เลิกบล็อกไมโครโฟนของอุปกรณ์ใช่ไหม"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"เลิกบล็อกกล้องของอุปกรณ์ใช่ไหม"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"เลิกบล็อกกล้องและไมโครโฟนของอุปกรณ์ใช่ไหม"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index a8cba0b4f7c4..aabba456d49c 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"nagpadala ng larawan"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Sine-save ang screenshot…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sine-save ang screenshot sa profile sa trabaho…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Sine-save ang screenshot sa pribado"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Na-save ang screenshot"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Hindi ma-save ang screenshot"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External na Display"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Magpares ng bagong device"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"I-click para magpares ng bagong device"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Hindi ma-update ang preset"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"I-unblock ang mikropono ng device?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"I-unblock ang camera ng device?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"I-unblock ang camera at mikropono ng device?"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index d3fceeb9419c..db84644d7a5e 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"bir resim gönderildi"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Ekran görüntüsü kaydediliyor..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekran görüntüsü iş profiline kaydediliyor…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Ekran görüntüsü özel profile kaydediliyor"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekran görüntüsü kaydedildi"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Ekran görüntüsü kaydedilemedi"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Harici Ekran"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Yeni cihaz eşle"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Yeni cihaz eşlemek için tıklayın"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Hazır ayar güncellenemedi"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Cihaz mikrofonunun engellemesi kaldırılsın mı?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Cihaz kamerasının engellemesi kaldırılsın mı?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Cihaz kamerası ile mikrofonunun engellemesi kaldırılsın mı?"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 662031cd835d..1afcf2ac9065 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"надіслане зображення"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Збереження знімка екрана..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Зберігання знімка екрана в робочому профілі…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Збереження знімка екрана в приватному профілі"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Знімок екрана збережено"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Не вдалося зберегти знімок екрана"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Зовнішній дисплей"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Підключити новий пристрій"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Натисніть, щоб підключити новий пристрій"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Не вдалось оновити набір налаштувань"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Надати доступ до мікрофона?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Надати доступ до камери пристрою?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Надати доступ до камери й мікрофона?"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 24a3eb0bcee1..d7c4c88cd802 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ایک تصویر بھیجی"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"اسکرین شاٹ محفوظ ہو رہا ہے…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"اسکرین شاٹ دفتری پروفائل میں محفوظ کیا جا رہا ہے…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"اسکرین شاٹ کو نجی پروفائل میں محفوظ کیا جا رہا ہے"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"اسکرین شاٹ محفوظ ہو گیا"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکا"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"خارجی ڈسپلے"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"نئے آلے کا جوڑا بنائیں"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"نئے آلے کا جوڑا بنانے کے لیے کلک کریں"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"پہلے سے ترتیب شدہ کو اپ ڈیٹ نہیں کیا جا سکا"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"آلے کا مائیکروفون غیر مسدود کریں؟"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"آلے کا کیمرا غیر مسدود کریں؟"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"آلے کا کیمرا اور مائیکروفون غیر مسدود کریں؟"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 5cdff8bd44a1..55bdd8490342 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"rasm yuborildi"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Skrinshot saqlanmoqda…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Skrinshot ish profiliga saqlanmoqda…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Skrinshot shaxsiy profilga saqlanmoqda"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinshot saqlandi"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinshot saqlanmadi"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Tashqi displey"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Yangi qurilmani ulash"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Yangi qurilmani ulash uchun bosing"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Andoza yangilanmadi"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Qurilma mikrofoni blokdan chiqarilsinmi?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Qurilma kamerasi blokdan chiqarilsinmi?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Qurilma kamerasi va mikrofoni blokdan chiqarilsinmi?"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 7c9191620edf..fb1f28cd679a 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"đã gửi hình ảnh"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Đang lưu ảnh chụp màn hình..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Đang lưu ảnh chụp màn hình vào hồ sơ công việc…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Đang lưu ảnh chụp màn hình vào hồ sơ riêng tư"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Đã lưu ảnh chụp màn hình"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Không thể lưu ảnh chụp màn hình"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Màn hình bên ngoài"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Ghép nối thiết bị mới"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Nhấp để ghép nối thiết bị mới"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Không cập nhật được giá trị đặt trước"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Bỏ chặn micrô của thiết bị?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Bỏ chặn camera của thiết bị?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Bỏ chặn máy ảnh và micrô của thiết bị?"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index dda17d41881b..1a3055c3f226 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"发送了一张图片"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"正在保存屏幕截图..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在将屏幕截图保存到工作资料…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"正在将屏幕截图保存到个人资料"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"已保存屏幕截图"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"无法保存屏幕截图"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"外部显示屏"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"与新设备配对"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"点击即可与新设备配对"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"无法更新预设"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"要解锁设备麦克风吗?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"要解锁设备摄像头吗?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"要解锁设备摄像头和麦克风吗?"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index f48d3229f2ec..8523972ef9ab 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"已傳送圖片"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"正在儲存螢幕擷取畫面..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存至工作設定檔…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"正在將螢幕截圖儲存至個人設定檔"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕擷取畫面已儲存"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕擷取畫面"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"外部顯示屏"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"配對新裝置"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"㩒一下就可以配對新裝置"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"無法更新預設"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"要解除封鎖裝置麥克風嗎?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"要解除封鎖裝置相機嗎?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"要解除封鎖裝置相機和麥克風嗎?"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index af1d9158e957..8d5cad40b295 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"傳送了一張圖片"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"正在儲存螢幕截圖…"</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存到工作資料夾…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"正在將螢幕截圖儲存到個人資料夾"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕截圖已儲存"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕截圖"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"外接螢幕"</string> @@ -278,7 +279,7 @@ <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"取消連結"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟用"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自動重新開啟"</string> - <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"「快速分享」和「尋找我的裝置」等功能需要藍牙"</string> + <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"「快速分享」和「尋找我的裝置」等功能都需要使用藍牙技術"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"藍牙會在明天早上開啟"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"音訊分享"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"正在分享音訊"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"配對新裝置"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"按一下即可配對新裝置"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"無法更新預設設定"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"要解除封鎖裝置麥克風嗎?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"要解除封鎖裝置相機嗎?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"要將裝置的相機和麥克風解除封鎖嗎?"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index a4673db7a84c..6644b53591a8 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -76,6 +76,7 @@ <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"uthumele isithombe"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Ilondoloz umfanekiso weskrini..."</string> <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ilondoloza isithombe-skrini kuphrofayela yomsebenzi…"</string> + <string name="screenshot_saving_private_profile" msgid="8934706048497093297">"Ilondoloza isithombe-skrini sibe esigodliwe"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Isithombe-skrini silondoloziwe"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Ayikwazanga ukulondoloza isithombe-skrini"</string> <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"Isiboniso Sangaphandle"</string> @@ -271,7 +272,7 @@ <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Thepha ukuze uxhumae noma ungaxhumi idivaysi"</string> <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Bhangqa idivayisi entsha"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Buka konke"</string> - <string name="turn_on_bluetooth" msgid="5681370462180289071">"Sebenzisa i-Bluetooth"</string> + <string name="turn_on_bluetooth" msgid="5681370462180289071">"Sebenzisa iBluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ixhunyiwe"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ukwabelana Ngokuqoshiwe"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ilondoloziwe"</string> @@ -279,7 +280,7 @@ <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"yenza kusebenze"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Vula ngokuzenzekela futhi kusasa"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Izakhi ezifana nokuthi Ukwabelana Ngokushesha kanye nokuthi Thola Idivayisi Yami zisebenzisa i-Bluetooth"</string> - <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"I-Bluetooth izovuleka kusasa ekuseni"</string> + <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"IBluetooth izovuleka kusasa ekuseni"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Ukwabelana Ngokuqoshiwe"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Ukwabelana Ngomsindo"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ibhethri"</string> @@ -371,6 +372,8 @@ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Bhangqa idivayisi entsha"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Chofoza ukuze ubhangqe idivayisi entsha"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ayikwazanga ukubuyekeza ukusetha ngaphambilini"</string> + <!-- no translation found for hearing_devices_preset_label (7878267405046232358) --> + <skip /> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vulela imakrofoni yedivayisi?"</string> <string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Vulela ikhamera yedivayisi?"</string> <string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Vulela ikhamera yedivayisi nemakrofoni?"</string> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 638785402055..fb883640c9a9 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -125,6 +125,20 @@ <!-- Use collapsed layout for media player in landscape QQS --> <bool name="config_quickSettingsMediaLandscapeCollapsed">true</bool> + <!-- For hearing devices related tool list. Need to be in ComponentName format (package/class). + Should be activity to be launched. + Already contains tool that holds intent: "com.android.settings.action.live_caption". + Maximum number is 3. --> + <string-array name="config_quickSettingsHearingDevicesRelatedToolName" translatable="false"> + </string-array> + + <!-- The drawable resource names. If provided, it will replace the corresponding icons in + config_quickSettingsHearingDevicesRelatedToolName. Can be empty to use original icons. + Already contains tool that holds intent: "com.android.settings.action.live_caption". + Maximum number is 3. --> + <string-array name="config_quickSettingsHearingDevicesRelatedToolIcon" translatable="false"> + </string-array> + <!-- Show indicator for Wifi on but not connected. --> <bool name="config_showWifiIndicatorWhenEnabled">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 02b74ce14088..8ce20684d892 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -713,7 +713,6 @@ <dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen> <dimen name="qs_header_carrier_separator_width">6dp</dimen> <dimen name="qs_carrier_margin_width">4dp</dimen> - <dimen name="qs_footer_icon_size">20dp</dimen> <dimen name="qs_header_height">120dp</dimen> <dimen name="qs_header_row_min_height">48dp</dimen> @@ -721,11 +720,7 @@ <dimen name="new_qs_header_non_clickable_element_height">24sp</dimen> <dimen name="qs_footer_padding">20dp</dimen> - <dimen name="qs_security_footer_height">88dp</dimen> - <dimen name="qs_security_footer_single_line_height">48dp</dimen> <dimen name="qs_footers_margin_bottom">8dp</dimen> - <dimen name="qs_security_footer_background_inset">0dp</dimen> - <dimen name="qs_security_footer_corner_radius">28dp</dimen> <dimen name="segmented_button_spacing">0dp</dimen> <dimen name="borderless_button_radius">2dp</dimen> @@ -1119,11 +1114,18 @@ <dimen name="biometric_dialog_height">240dp</dimen> <!-- Dimensions for biometric prompt panel padding --> - <dimen name="biometric_prompt_small_horizontal_guideline_padding">344dp</dimen> - <dimen name="biometric_prompt_udfps_horizontal_guideline_padding">114dp</dimen> - <dimen name="biometric_prompt_udfps_mid_guideline_padding">409dp</dimen> - <dimen name="biometric_prompt_medium_horizontal_guideline_padding">640dp</dimen> - <dimen name="biometric_prompt_medium_mid_guideline_padding">330dp</dimen> + <dimen name="biometric_prompt_panel_max_width">640dp</dimen> + <dimen name="biometric_prompt_land_small_horizontal_guideline_padding">344dp</dimen> + <dimen name="biometric_prompt_two_pane_udfps_horizontal_guideline_padding">114dp</dimen> + <dimen name="biometric_prompt_two_pane_udfps_mid_guideline_padding">409dp</dimen> + <dimen name="biometric_prompt_two_pane_medium_horizontal_guideline_padding">640dp</dimen> + <dimen name="biometric_prompt_two_pane_medium_mid_guideline_padding">330dp</dimen> + <dimen name="biometric_prompt_one_pane_medium_top_guideline_padding">119dp</dimen> + <dimen name="biometric_prompt_one_pane_medium_horizontal_guideline_padding">0dp</dimen> + + <!-- Dimensions for biometric prompt scroll view padding --> + <dimen name="biometric_prompt_top_scroll_view_bottom_padding">24dp</dimen> + <dimen name="biometric_prompt_top_scroll_view_horizontal_padding">24dp</dimen> <!-- Dimensions for biometric prompt icon padding --> <dimen name="biometric_prompt_portrait_small_bottom_padding">60dp</dimen> @@ -1136,6 +1138,7 @@ <!-- Dimensions for biometric prompt custom content view. --> <dimen name="biometric_prompt_logo_size">32dp</dimen> + <dimen name="biometric_prompt_logo_description_top_padding">8dp</dimen> <dimen name="biometric_prompt_content_corner_radius">28dp</dimen> <dimen name="biometric_prompt_content_padding_horizontal">24dp</dimen> <dimen name="biometric_prompt_content_padding_vertical">16dp</dimen> @@ -1772,12 +1775,15 @@ <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> + <dimen name="hearing_devices_tool_icon_frame_width">94dp</dimen> + <dimen name="hearing_devices_tool_icon_frame_height">64dp</dimen> + <dimen name="hearing_devices_tool_icon_size">28dp</dimen> <!-- Height percentage of the parent container occupied by the communal view --> <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index b993a5ad75b9..177ba598add7 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -259,9 +259,6 @@ <!-- ID of the Scene Container root Composable view --> <item type='id' name="scene_container_root_composable" /> - <!-- Tag set on the Compose implementation of the QS footer actions. --> - <item type="id" name="tag_compose_qs_footer_actions" /> - <!-- Ids for the device entry icon. device_entry_icon_view: parent view of both device_entry_icon and device_entry_icon_bg diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index abfdc2a79603..c038a8207d43 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -913,8 +913,12 @@ <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> + <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40]--> + <string name="live_caption_title">Live Caption</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/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b8f71c10dc89..64717fcc8c5d 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -149,11 +149,6 @@ <item name="android:letterSpacing">0.01</item> </style> - <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> - <item name="android:textColor">?attr/onSurface</item> - </style> - <style name="TextAppearance.QS.Status.Carriers" /> <style name="TextAppearance.QS.Status.Carriers.NoCarrierText"> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt index b99c51489521..44f2059d4e41 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt @@ -22,15 +22,28 @@ sealed interface PromptKind { data class Biometric( /** The available modalities for the authentication on the prompt. */ val activeModalities: BiometricModalities = BiometricModalities(), - // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of - // simply depending on rotations. - val showTwoPane: Boolean = false, - ) : PromptKind + val paneType: PaneType = PaneType.ONE_PANE_PORTRAIT, + ) : PromptKind { + enum class PaneType { + TWO_PANE_LANDSCAPE, + ONE_PANE_PORTRAIT, + ONE_PANE_NO_SENSOR_LANDSCAPE, + ONE_PANE_LARGE_SCREEN_LANDSCAPE + } + } - object Pin : PromptKind - object Pattern : PromptKind - object Password : PromptKind + data object Pin : PromptKind + data object Pattern : PromptKind + data object Password : PromptKind fun isBiometric() = this is Biometric - fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password) + fun isTwoPaneLandscapeBiometric(): Boolean = + (this as? Biometric)?.paneType == Biometric.PaneType.TWO_PANE_LANDSCAPE + fun isOnePanePortraitBiometric() = + (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_PORTRAIT + fun isOnePaneNoSensorLandscapeBiometric() = + (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_NO_SENSOR_LANDSCAPE + fun isOnePaneLargeScreenLandscapeBiometric() = + (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_LARGE_SCREEN_LANDSCAPE + fun isCredential() = (this is Pin) || (this is Pattern) || (this is Password) } 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/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 3f3bb0bc94b6..86c807bf9d07 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -15,12 +15,12 @@ */ package com.android.keyguard -import android.os.Trace import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.res.Resources +import android.os.Trace import android.text.format.DateFormat import android.util.Log import android.util.TypedValue @@ -466,15 +466,11 @@ constructor( largeRegionSampler?.stopRegionSampler() smallTimeListener?.stop() largeTimeListener?.stop() - clock - ?.smallClock - ?.view - ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + clock?.apply { + smallClock.view.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + largeClock.view.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) + } smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener) - clock - ?.largeClock - ?.view - ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) } /** @@ -505,15 +501,17 @@ constructor( } } - private fun updateFontSizes() { + fun updateFontSizes() { clock?.run { - smallClock.events.onFontSettingChanged( - resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() - ) + smallClock.events.onFontSettingChanged(getSmallClockSizePx()) largeClock.events.onFontSettingChanged(getLargeClockSizePx()) } } + private fun getSmallClockSizePx(): Float { + return resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() + } + private fun getLargeClockSizePx(): Float { return if (largeClockOnSecondaryDisplay) { resources.getDimensionPixelSize(R.dimen.presentation_clock_text_size).toFloat() @@ -549,9 +547,8 @@ constructor( it.copy(value = 1f - it.value) }, keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)), - ).filter { - it.transitionState != TransitionState.FINISHED - } + ) + .filter { it.transitionState != TransitionState.FINISHED } .collect { handleDoze(it.value) } } } @@ -574,28 +571,27 @@ constructor( internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job { return scope.launch { keyguardTransitionInteractor - .transitionStepsToState(LOCKSCREEN) - .filter { it.transitionState == TransitionState.STARTED } - .filter { it.from != AOD } - .collect { handleDoze(0f) } + .transitionStepsToState(LOCKSCREEN) + .filter { it.transitionState == TransitionState.STARTED } + .filter { it.from != AOD } + .collect { handleDoze(0f) } } } /** - * When keyguard is displayed due to pulsing notifications when AOD is off, - * we should make sure clock is in dozing state instead of LS state + * When keyguard is displayed due to pulsing notifications when AOD is off, we should make sure + * clock is in dozing state instead of LS state */ @VisibleForTesting internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job { return scope.launch { keyguardTransitionInteractor - .transitionStepsToState(DOZING) - .filter { it.transitionState == TransitionState.FINISHED } - .collect { handleDoze(1f) } + .transitionStepsToState(DOZING) + .filter { it.transitionState == TransitionState.FINISHED } + .collect { handleDoze(1f) } } } - @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { return scope.launch { 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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 27b2b92ab899..63ad41a808dc 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -20,7 +20,6 @@ import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat; import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS; -import static com.android.systemui.flags.Flags.SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import android.animation.Animator; @@ -481,16 +480,11 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { updateSwipeProgressFromOffset(animView, canBeDismissed); mDismissPendingMap.remove(animView); boolean wasRemoved = false; - if (animView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) animView; - if (mFeatureFlags.isEnabled(SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX)) { - // If the view is already removed from its parent and added as Transient, - // we need to clean the transient view upon animation end - wasRemoved = row.getTransientContainer() != null - || row.getParent() == null || row.isRemoved(); - } else { - wasRemoved = row.isRemoved(); - } + if (animView instanceof ExpandableNotificationRow row) { + // If the view is already removed from its parent and added as Transient, + // we need to clean the transient view upon animation end + wasRemoved = row.getTransientContainer() != null + || row.getParent() == null || row.isRemoved(); } if (!mCancelled || wasRemoved) { mCallback.onChildDismissed(animView); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt index 4069cece7790..63791f9228c0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt @@ -30,6 +30,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile import com.android.systemui.qs.tiles.FontScalingTile +import com.android.systemui.qs.tiles.HearingDevicesTile import com.android.systemui.qs.tiles.OneHandedModeTile import com.android.systemui.qs.tiles.ReduceBrightColorsTile import javax.inject.Inject @@ -74,6 +75,8 @@ constructor( .REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME, FontScalingTile.TILE_SPEC to AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME, + HearingDevicesTile.TILE_SPEC to + AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME ) } 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..961d6aa1b821 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -25,17 +25,25 @@ import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHapPresetInfo; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; +import android.util.Log; 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; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -68,6 +76,7 @@ import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -77,12 +86,15 @@ import java.util.stream.Collectors; */ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, HearingDeviceItemCallback, BluetoothCallback { - + private static final String TAG = "HearingDevicesDialogDelegate"; @VisibleForTesting static final String ACTION_BLUETOOTH_DEVICE_DETAILS = "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"; private static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; private static final String KEY_BLUETOOTH_ADDRESS = "device_address"; + @VisibleForTesting + static final Intent LIVE_CAPTION_INTENT = new Intent( + "com.android.settings.action.live_caption"); private final SystemUIDialog.Factory mSystemUIDialogFactory; private final DialogTransitionAnimator mDialogTransitionAnimator; private final ActivityStarter mActivityStarter; @@ -101,6 +113,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private Spinner mPresetSpinner; private ArrayAdapter<String> mPresetInfoAdapter; private Button mPairButton; + private LinearLayout mRelatedToolsContainer; private final HearingDevicesPresetsController.PresetCallback mPresetCallback = new HearingDevicesPresetsController.PresetCallback() { @Override @@ -208,6 +221,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); @@ -248,10 +265,14 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mPairButton = dialog.requireViewById(R.id.pair_new_device_button); mDeviceList = dialog.requireViewById(R.id.device_list); mPresetSpinner = dialog.requireViewById(R.id.preset_spinner); + mRelatedToolsContainer = dialog.requireViewById(R.id.related_tools_container); setupDeviceListView(dialog); setupPresetSpinner(dialog); setupPairNewDeviceButton(dialog, mShowPairNewDevice ? VISIBLE : GONE); + if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) { + setupRelatedToolsView(dialog); + } } @Override @@ -295,10 +316,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) { @@ -333,6 +367,34 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, } } + private void setupRelatedToolsView(SystemUIDialog dialog) { + final Context context = dialog.getContext(); + final List<ToolItem> toolItemList = new ArrayList<>(); + final String[] toolNameArray; + final String[] toolIconArray; + + ToolItem preInstalledItem = getLiveCaption(context); + if (preInstalledItem != null) { + toolItemList.add(preInstalledItem); + } + try { + toolNameArray = context.getResources().getStringArray( + R.array.config_quickSettingsHearingDevicesRelatedToolName); + toolIconArray = context.getResources().getStringArray( + R.array.config_quickSettingsHearingDevicesRelatedToolIcon); + toolItemList.addAll( + HearingDevicesToolItemParser.parseStringArray(context, toolNameArray, + toolIconArray)); + } catch (Resources.NotFoundException e) { + Log.i(TAG, "No hearing devices related tool config resource"); + } + final int listSize = toolItemList.size(); + for (int i = 0; i < listSize; i++) { + View view = createHearingToolView(context, toolItemList.get(i)); + mRelatedToolsContainer.addView(view); + } + } + private void refreshPresetInfoAdapter(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex) { mPresetInfoAdapter.clear(); @@ -382,6 +444,40 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, return null; } + @NonNull + private View createHearingToolView(Context context, ToolItem item) { + View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, + mRelatedToolsContainer, false); + ImageView icon = view.requireViewById(R.id.tool_icon); + TextView text = view.requireViewById(R.id.tool_name); + view.setContentDescription(item.getToolName()); + icon.setImageDrawable(item.getToolIcon()); + text.setText(item.getToolName()); + Intent intent = item.getToolIntent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + view.setOnClickListener( + v -> { + dismissDialogIfExists(); + mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, + mDialogTransitionAnimator.createActivityTransitionController(view)); + }); + return view; + } + + private ToolItem getLiveCaption(Context context) { + final PackageManager packageManager = context.getPackageManager(); + LIVE_CAPTION_INTENT.setPackage(packageManager.getSystemCaptionsServicePackageName()); + final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT, + /* flags= */ 0); + if (!resolved.isEmpty()) { + return new ToolItem(context.getString(R.string.live_caption_title), + context.getDrawable(R.drawable.ic_volume_odi_captions), + LIVE_CAPTION_INTENT); + } + + return null; + } + private void dismissDialogIfExists() { if (mDialog != null) { mDialog.dismiss(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java new file mode 100644 index 000000000000..2006726e6847 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.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.systemui.accessibility.hearingaid; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Utility class for managing and parsing tool items related to hearing devices. + */ +public class HearingDevicesToolItemParser { + private static final String TAG = "HearingDevicesToolItemParser"; + private static final String SPLIT_DELIMITER = "/"; + private static final String RES_TYPE = "drawable"; + @VisibleForTesting + static final int MAX_NUM = 3; + + /** + * Parses the string arrays to create a list of {@link ToolItem}. + * + * This method validates the structure of {@code toolNameArray} and {@code toolIconArray}. + * If {@code toolIconArray} is empty or mismatched in length with {@code toolNameArray}, the + * icon from {@link ActivityInfo#loadIcon(PackageManager)} will be used instead. + * + * @param context A valid context. + * @param toolNameArray An array of tool names in the format of {@link ComponentName}. + * @param toolIconArray An optional array of resource names for tool icons (can be empty). + * @return A list of {@link ToolItem} or an empty list if there are errors during parsing. + */ + public static ImmutableList<ToolItem> parseStringArray(Context context, String[] toolNameArray, + String[] toolIconArray) { + if (toolNameArray.length == 0) { + Log.i(TAG, "Empty hearing device related tool name in array."); + return ImmutableList.of(); + } + // For the performance concern, especially `getIdentifier` in `parseValidIcon`, we will + // limit the maximum number. + String[] nameArrayCpy = Arrays.copyOfRange(toolNameArray, 0, + Math.min(toolNameArray.length, MAX_NUM)); + String[] iconArrayCpy = Arrays.copyOfRange(toolIconArray, 0, + Math.min(toolIconArray.length, MAX_NUM)); + + final PackageManager packageManager = context.getPackageManager(); + final ImmutableList.Builder<ToolItem> toolItemList = ImmutableList.builder(); + final List<ActivityInfo> activityInfoList = parseValidActivityInfo(context, nameArrayCpy); + final List<Drawable> iconList = parseValidIcon(context, iconArrayCpy); + final int size = activityInfoList.size(); + // Only use custom icon if provided icon's list size is equal to provided name's list size. + final boolean useCustomIcons = (size == iconList.size()); + + for (int i = 0; i < size; i++) { + toolItemList.add(new ToolItem( + activityInfoList.get(i).loadLabel(packageManager).toString(), + useCustomIcons ? iconList.get(i) + : activityInfoList.get(i).loadIcon(packageManager), + new Intent(Intent.ACTION_MAIN).setComponent( + activityInfoList.get(i).getComponentName()) + )); + } + + return toolItemList.build(); + } + + private static List<ActivityInfo> parseValidActivityInfo(Context context, + String[] toolNameArray) { + final PackageManager packageManager = context.getPackageManager(); + final List<ActivityInfo> activityInfoList = new ArrayList<>(); + for (String toolName : toolNameArray) { + String[] nameParts = toolName.split(SPLIT_DELIMITER); + if (nameParts.length == 2) { + ComponentName componentName = ComponentName.unflattenFromString(toolName); + try { + ActivityInfo activityInfo = packageManager.getActivityInfo( + componentName, /* flags= */ 0); + activityInfoList.add(activityInfo); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to find hearing device related tool: " + + componentName.flattenToString()); + } + } else { + Log.e(TAG, "Malformed hearing device related tool name item in array: " + + toolName); + } + } + return activityInfoList; + } + + private static List<Drawable> parseValidIcon(Context context, String[] toolIconArray) { + final List<Drawable> drawableList = new ArrayList<>(); + for (String icon : toolIconArray) { + int resId = context.getResources().getIdentifier(icon, RES_TYPE, + context.getPackageName()); + try { + drawableList.add(context.getDrawable(resId)); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Resource does not exist: " + icon); + } + } + return drawableList; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt index e20d003bb989..66bb2b5e2328 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt @@ -14,16 +14,13 @@ * limitations under the License. */ -package com.android.systemui.brightness.shared +package com.android.systemui.accessibility.hearingaid -import androidx.annotation.IntRange -import com.android.settingslib.display.BrightnessUtils +import android.content.Intent +import android.graphics.drawable.Drawable -@JvmInline -value class GammaBrightness( - @IntRange( - from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(), - to = BrightnessUtils.GAMMA_SPACE_MAX.toLong() - ) - val value: Int +data class ToolItem( + var toolName: String = "", + var toolIcon: Drawable, + var toolIntent: Intent, ) diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java index 019f498a01f8..f905241addeb 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java @@ -269,6 +269,7 @@ public class BouncerSwipeTouchHandler implements TouchHandler { } mScrimManager.removeCallback(mScrimManagerCallback); mCapture = null; + mTouchSession = null; if (!Flags.communalBouncerDoNotModifyPluginOpen()) { mNotificationShadeWindowController.setForcePluginOpen(false, this); diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java index 227e4dba5d04..61b4401d6b22 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java @@ -49,11 +49,14 @@ import com.android.systemui.util.display.DisplayHelper; import com.google.common.util.concurrent.ListenableFuture; +import kotlinx.coroutines.Job; + import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -78,15 +81,7 @@ public class TouchMonitor { private final Lifecycle mLifecycle; private Rect mExclusionRect = null; - private ISystemGestureExclusionListener mGestureExclusionListener = - new ISystemGestureExclusionListener.Stub() { - @Override - public void onSystemGestureExclusionChanged(int displayId, - Region systemGestureExclusion, - Region systemGestureExclusionUnrestricted) { - mExclusionRect = systemGestureExclusion.getBounds(); - } - }; + private ISystemGestureExclusionListener mGestureExclusionListener; private Consumer<Rect> mMaxBoundsConsumer = rect -> mMaxBounds = rect; @@ -274,6 +269,14 @@ public class TouchMonitor { if (bouncerAreaExclusion()) { mBackgroundExecutor.execute(() -> { try { + mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { + @Override + public void onSystemGestureExclusionChanged(int displayId, + Region systemGestureExclusion, + Region systemGestureExclusionUnrestricted) { + mExclusionRect = systemGestureExclusion.getBounds(); + } + }; mWindowManagerService.registerSystemGestureExclusionListener( mGestureExclusionListener, mDisplayId); } catch (RemoteException e) { @@ -298,8 +301,11 @@ public class TouchMonitor { if (bouncerAreaExclusion()) { mBackgroundExecutor.execute(() -> { try { - mWindowManagerService.unregisterSystemGestureExclusionListener( - mGestureExclusionListener, mDisplayId); + if (mGestureExclusionListener != null) { + mWindowManagerService.unregisterSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + mGestureExclusionListener = null; + } } catch (RemoteException e) { // Handle the exception Log.e(TAG, "unregisterSystemGestureExclusionListener: failed", e); @@ -494,6 +500,10 @@ public class TouchMonitor { private Rect mMaxBounds; + private Job mBoundsFlow; + + private boolean mInitialized; + /** * Designated constructor for {@link TouchMonitor} @@ -535,10 +545,35 @@ public class TouchMonitor { * Initializes the monitor. should only be called once after creation. */ public void init() { + if (mInitialized) { + throw new IllegalStateException("TouchMonitor already initialized"); + } + mLifecycle.addObserver(mLifecycleObserver); if (Flags.ambientTouchMonitorListenToDisplayChanges()) { - collectFlow(mLifecycle, mConfigurationInteractor.getMaxBounds(), mMaxBoundsConsumer); + mBoundsFlow = collectFlow(mLifecycle, mConfigurationInteractor.getMaxBounds(), + mMaxBoundsConsumer); } + + mInitialized = true; + } + + /** + * Called when the TouchMonitor should be discarded and will not be used anymore. + */ + public void destroy() { + if (!mInitialized) { + throw new IllegalStateException("TouchMonitor not initialized"); + } + + stopMonitoring(true); + + mLifecycle.removeObserver(mLifecycleObserver); + if (Flags.ambientTouchMonitorListenToDisplayChanges()) { + mBoundsFlow.cancel(new CancellationException()); + } + + mInitialized = false; } private void isolate(Set<TouchSessionImpl> sessions) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index b75b292be597..1ee4908437a6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlertDialog; import android.content.Context; +import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PixelFormat; @@ -360,15 +361,23 @@ public class AuthContainerView extends LinearLayout Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds)); + final boolean isLandscape = mContext.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mEffectiveUserId, getRequestId(), biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName, - false /*onSwitchToCredential*/); + false /*onSwitchToCredential*/, isLandscape); final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - if (constraintBp() && mPromptViewModel.getPromptKind().getValue().isBiometric()) { - mLayout = (ConstraintLayout) layoutInflater.inflate( - R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */); + final PromptKind kind = mPromptViewModel.getPromptKind().getValue(); + if (constraintBp() && kind.isBiometric()) { + if (kind.isTwoPaneLandscapeBiometric()) { + mLayout = (ConstraintLayout) layoutInflater.inflate( + R.layout.biometric_prompt_two_pane_layout, this, false /* attachToRoot */); + } else { + mLayout = (ConstraintLayout) layoutInflater.inflate( + R.layout.biometric_prompt_one_pane_layout, this, false /* attachToRoot */); + } } else { mLayout = (FrameLayout) layoutInflater.inflate( R.layout.auth_container_view, this, false /* attachToRoot */); @@ -631,7 +640,7 @@ public class AuthContainerView extends LinearLayout if (fpProp != null && fpProp.isAnyUdfpsType()) { maybeUpdatePositionForUdfps(forceInvalidate /* invalidate */); } - if (faceProp != null && mBiometricView.isFaceOnly()) { + if (faceProp != null && mBiometricView != null && mBiometricView.isFaceOnly()) { alwaysUpdatePositionAtScreenBottom(forceInvalidate /* invalidate */); } if (fpProp != null && fpProp.sensorType == TYPE_POWER_BUTTON) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt index 8e5a97bd5d8d..9b14d6f68e35 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt @@ -29,11 +29,10 @@ import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY import com.android.systemui.display.data.repository.DisplayRepository import javax.inject.Inject +import kotlin.math.min import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -58,7 +57,7 @@ interface DisplayStateRepository { val currentDisplaySize: StateFlow<Size> /** Provides whether the current display is large screen */ - val isLargeScreen: Flow<Boolean> + val isLargeScreen: StateFlow<Boolean> } @SysUISingleton @@ -127,16 +126,29 @@ constructor( ), ) - override val isLargeScreen: Flow<Boolean> = + override val isLargeScreen: StateFlow<Boolean> = currentDisplayInfo .map { - // TODO: This works, but investigate better way to handle this - it.logicalWidth * 160 / it.logicalDensityDpi > DisplayMetrics.DENSITY_XXXHIGH && - it.logicalHeight * 160 / it.logicalDensityDpi > DisplayMetrics.DENSITY_XXHIGH + // copied from systemui/shared/...Utilities.java + val smallestWidth = + dpiFromPx( + min(it.logicalWidth, it.logicalHeight).toFloat(), + context.resources.configuration.densityDpi + ) + smallestWidth >= LARGE_SCREEN_MIN_DPS } - .distinctUntilChanged() + .stateIn( + backgroundScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + private fun dpiFromPx(size: Float, densityDpi: Int): Float { + val densityRatio = densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT + return size / densityRatio + } companion object { const val TAG = "DisplayStateRepositoryImpl" + const val LARGE_SCREEN_MIN_DPS = 600f } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt index 591da4096956..40313e3158aa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt @@ -65,7 +65,8 @@ interface DisplayStateInteractor { /** Called on configuration changes, used to keep the display state in sync */ fun onConfigurationChanged(newConfig: Configuration) - val isLargeScreen: Flow<Boolean> + /** Provides whether the current display is large screen */ + val isLargeScreen: StateFlow<Boolean> } /** Encapsulates logic for interacting with the display state. */ @@ -127,7 +128,7 @@ constructor( override val isDefaultDisplayOff = displayRepository.defaultDisplayOff - override val isLargeScreen: Flow<Boolean> = displayStateRepository.isLargeScreen + override val isLargeScreen: StateFlow<Boolean> = displayStateRepository.isLargeScreen companion object { private const val TAG = "DisplayStateInteractor" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index dc338d07f9e7..c08756f6ae36 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -91,6 +91,7 @@ interface PromptSelectorInteractor { challenge: Long, opPackageName: String, onSwitchToCredential: Boolean, + isLandscape: Boolean, ) /** Unset the current authentication request. */ @@ -102,6 +103,7 @@ class PromptSelectorInteractorImpl @Inject constructor( fingerprintPropertyRepository: FingerprintPropertyRepository, + private val displayStateInteractor: DisplayStateInteractor, private val promptRepository: PromptRepository, private val lockPatternUtils: LockPatternUtils, ) : PromptSelectorInteractor { @@ -166,7 +168,9 @@ constructor( modalities, promptRepository.challenge.value!!, promptRepository.opPackageName.value!!, - true /*onSwitchToCredential*/ + onSwitchToCredential = true, + // isLandscape value is not important when onSwitchToCredential is true + isLandscape = false, ) } @@ -178,6 +182,7 @@ constructor( challenge: Long, opPackageName: String, onSwitchToCredential: Boolean, + isLandscape: Boolean, ) { val hasCredentialViewShown = promptKind.value.isCredential() val showBpForCredential = @@ -189,11 +194,30 @@ constructor( !promptInfo.isContentViewMoreOptionsButtonUsed val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown var kind: PromptKind = PromptKind.None + if (onSwitchToCredential) { kind = getCredentialType(lockPatternUtils, effectiveUserId) } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) { - // TODO(b/330908557): check to show one pane or two pane - kind = PromptKind.Biometric(modalities) + // TODO(b/330908557): Subscribe to + // displayStateInteractor.currentRotation.value.isDefaultOrientation() for checking + // `isLandscape` after removing AuthContinerView. + kind = + if (isLandscape) { + val paneType = + when { + displayStateInteractor.isLargeScreen.value -> + PromptKind.Biometric.PaneType.ONE_PANE_LARGE_SCREEN_LANDSCAPE + showBpWithoutIconForCredential -> + PromptKind.Biometric.PaneType.ONE_PANE_NO_SENSOR_LANDSCAPE + else -> PromptKind.Biometric.PaneType.TWO_PANE_LANDSCAPE + } + PromptKind.Biometric( + modalities, + paneType = paneType, + ) + } else { + PromptKind.Biometric(modalities) + } } else if (isDeviceCredentialAllowed(promptInfo)) { kind = getCredentialType(lockPatternUtils, effectiveUserId) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index 47174c006735..c836f89a8ff4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -93,6 +93,7 @@ object BiometricViewSizeBinder { if (constraintBp()) { val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline) + val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline) val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline) val midGuideline = view.findViewById<Guideline>(R.id.midGuideline) @@ -355,6 +356,18 @@ object BiometricViewSizeBinder { ) } + if (bounds.top >= 0) { + mediumConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top) + smallConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top) + } else if (bounds.top < 0) { + mediumConstraintSet.setGuidelineEnd( + topGuideline.id, + abs(bounds.top) + ) + smallConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top)) + } + + // Use rect bottom to set mid guideline of two-pane. if (midGuideline != null) { if (bounds.bottom >= 0) { midGuideline.setGuidelineEnd(bounds.bottom) 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/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 156ec6b975a5..c17b83dd4fbe 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -261,10 +261,13 @@ constructor( combine( _forceLargeSize, displayStateInteractor.isLargeScreen, - displayStateInteractor.currentRotation + displayStateInteractor.currentRotation, ) { forceLarge, isLargeScreen, rotation -> when { - forceLarge || isLargeScreen -> PromptPosition.Bottom + forceLarge || + isLargeScreen || + promptKind.value.isOnePaneNoSensorLandscapeBiometric() -> + PromptPosition.Bottom rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left rotation == DisplayRotation.ROTATION_180 -> PromptPosition.Top @@ -297,23 +300,27 @@ constructor( /** Prompt panel size padding */ private val smallHorizontalGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_small_horizontal_guideline_padding + R.dimen.biometric_prompt_land_small_horizontal_guideline_padding ) private val udfpsHorizontalGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_udfps_horizontal_guideline_padding + R.dimen.biometric_prompt_two_pane_udfps_horizontal_guideline_padding ) private val udfpsMidGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_udfps_mid_guideline_padding + R.dimen.biometric_prompt_two_pane_udfps_mid_guideline_padding + ) + private val mediumTopGuidelinePadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_one_pane_medium_top_guideline_padding ) private val mediumHorizontalGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_medium_horizontal_guideline_padding + R.dimen.biometric_prompt_two_pane_medium_horizontal_guideline_padding ) private val mediumMidGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_medium_mid_guideline_padding + R.dimen.biometric_prompt_two_pane_medium_mid_guideline_padding ) /** Rect for positioning biometric icon */ @@ -416,9 +423,9 @@ constructor( * asset to be loaded before determining the prompt size. */ val isIconViewLoaded: Flow<Boolean> = - combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded -> - val noIcon = modalities.isEmpty - noIcon || isIconViewLoaded + combine(hideSensorIcon, _isIconViewLoaded.asStateFlow()) { hideSensorIcon, isIconViewLoaded + -> + hideSensorIcon || isIconViewLoaded } .distinctUntilChanged() @@ -448,17 +455,24 @@ constructor( * from opposite side of the screen */ val guidelineBounds: Flow<Rect> = - combine(iconPosition, size, position, modalities) { _, size, position, modalities -> + combine(iconPosition, promptKind, size, position, modalities) { + _, + promptKind, + size, + position, + modalities -> when (position) { - PromptPosition.Bottom -> Rect(0, 0, 0, 0) + PromptPosition.Bottom -> + if (promptKind.isOnePaneNoSensorLandscapeBiometric()) { + Rect(0, 0, 0, 0) + } else { + Rect(0, mediumTopGuidelinePadding, 0, 0) + } PromptPosition.Right -> if (size.isSmall) { Rect(-smallHorizontalGuidelinePadding, 0, 0, 0) } else if (modalities.hasUdfps) { Rect(udfpsHorizontalGuidelinePadding, 0, 0, udfpsMidGuidelinePadding) - } else if (modalities.isEmpty) { - // TODO: Temporary fix until no biometric landscape layout is added - Rect(-mediumHorizontalGuidelinePadding, 0, 0, 6) } else { Rect(-mediumHorizontalGuidelinePadding, 0, 0, mediumMidGuidelinePadding) } @@ -467,9 +481,6 @@ constructor( Rect(0, 0, -smallHorizontalGuidelinePadding, 0) } else if (modalities.hasUdfps) { Rect(0, 0, udfpsHorizontalGuidelinePadding, -udfpsMidGuidelinePadding) - } else if (modalities.isEmpty) { - // TODO: Temporary fix until no biometric landscape layout is added - Rect(0, 0, -mediumHorizontalGuidelinePadding, -6) } else { Rect( 0, 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/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 7c41b75d7105..40a141dcec77 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -20,6 +20,8 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources import android.content.Context import android.graphics.Bitmap +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.type import androidx.core.graphics.drawable.toBitmap import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.SceneKey @@ -326,7 +328,8 @@ class BouncerViewModel( { message }, failedAttempts, remainingAttempts, - ) ?: message + ) + ?: message } else { message } @@ -343,7 +346,8 @@ class BouncerViewModel( .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE, { message }, failedAttempts, - ) ?: message + ) + ?: message } else { message } @@ -377,6 +381,19 @@ class BouncerViewModel( Swipe(SwipeDirection.Down) to UserActionResult(prevScene), ) + /** + * Notifies that a key event has occurred. + * + * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise. + */ + fun onKeyEvent(keyEvent: KeyEvent): Boolean { + return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent( + keyEvent.type, + keyEvent.nativeKeyEvent.keyCode + ) + ?: false + } + data class DialogViewModel( val text: String, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 4c2380c5e4db..aa447ffac154 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -19,6 +19,14 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context +import android.view.KeyEvent.KEYCODE_0 +import android.view.KeyEvent.KEYCODE_9 +import android.view.KeyEvent.KEYCODE_DEL +import android.view.KeyEvent.KEYCODE_NUMPAD_0 +import android.view.KeyEvent.KEYCODE_NUMPAD_9 +import android.view.KeyEvent.isConfirmKey +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType import com.android.keyguard.PinShapeAdapter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -196,6 +204,44 @@ class PinBouncerViewModel( else -> ActionButtonAppearance.Shown } } + + /** + * Notifies that a key event has occurred. + * + * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise. + */ + fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean { + return when (type) { + KeyEventType.KeyUp -> { + if (isConfirmKey(keyCode)) { + onAuthenticateButtonClicked() + true + } else { + false + } + } + KeyEventType.KeyDown -> { + when (keyCode) { + KEYCODE_DEL -> { + onBackspaceButtonClicked() + true + } + in KEYCODE_0..KEYCODE_9 -> { + onPinButtonClicked(keyCode - KEYCODE_0) + true + } + in KEYCODE_NUMPAD_0..KEYCODE_NUMPAD_9 -> { + onPinButtonClicked(keyCode - KEYCODE_NUMPAD_0) + true + } + else -> { + false + } + } + } + else -> false + } + } } /** Appearance of pin-pad action buttons. */ diff --git a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt index 2b9fc73458d8..7a9429e56c88 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt @@ -20,8 +20,15 @@ import com.android.systemui.brightness.data.repository.BrightnessPolicyRepositor import com.android.systemui.brightness.data.repository.BrightnessPolicyRepositoryImpl import com.android.systemui.brightness.data.repository.ScreenBrightnessDisplayManagerRepository import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository +import com.android.systemui.brightness.shared.model.BrightnessLog +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory import dagger.Binds import dagger.Module +import dagger.Provides @Module interface ScreenBrightnessModule { @@ -33,4 +40,20 @@ interface ScreenBrightnessModule { @Binds fun bindPolicyRepository(impl: BrightnessPolicyRepositoryImpl): BrightnessPolicyRepository + + companion object { + @Provides + @SysUISingleton + @BrightnessLog + fun providesBrightnessTableLog(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("BrightnessTableLog", 50) + } + + @Provides + @SysUISingleton + @BrightnessLog + fun providesBrightnessLog(factory: LogBufferFactory): LogBuffer { + return factory.create("BrightnessLog", 50) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt index 9ed11d13d4d4..37d1887730b9 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt @@ -19,12 +19,18 @@ package com.android.systemui.brightness.data.repository import android.annotation.SuppressLint import android.hardware.display.BrightnessInfo import android.hardware.display.DisplayManager -import com.android.systemui.brightness.data.model.LinearBrightness +import com.android.systemui.brightness.shared.model.BrightnessLog +import com.android.systemui.brightness.shared.model.LinearBrightness +import com.android.systemui.brightness.shared.model.formatBrightness +import com.android.systemui.brightness.shared.model.logDiffForTable import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.DisplayId +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.table.TableLogBuffer import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope @@ -32,13 +38,13 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -78,6 +84,8 @@ class ScreenBrightnessDisplayManagerRepository constructor( @DisplayId private val displayId: Int, private val displayManager: DisplayManager, + @BrightnessLog private val logBuffer: LogBuffer, + @BrightnessLog private val tableBuffer: TableLogBuffer, @Application private val applicationScope: CoroutineScope, @Background private val backgroundContext: CoroutineContext, ) : ScreenBrightnessRepository { @@ -100,6 +108,7 @@ constructor( displayManager.setBrightness(displayId, value) } } + logBrightnessChange(call is SetBrightnessMethod.Permanent, value) } } } @@ -147,13 +156,15 @@ constructor( brightnessInfo .filterNotNull() .map { LinearBrightness(it.brightnessMinimum) } - .shareIn(applicationScope, SharingStarted.WhileSubscribed()) + .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MIN, null) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f)) - override val maxLinearBrightness = + override val maxLinearBrightness: SharedFlow<LinearBrightness> = brightnessInfo .filterNotNull() .map { LinearBrightness(it.brightnessMaximum) } - .shareIn(applicationScope, SharingStarted.WhileSubscribed()) + .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MAX, null) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(1f)) override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> { val brightnessInfo = brightnessInfo.value ?: brightnessInfoValue() @@ -166,7 +177,8 @@ constructor( brightnessInfo .filterNotNull() .map { LinearBrightness(it.brightness) } - .shareIn(applicationScope, SharingStarted.WhileSubscribed()) + .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_BRIGHTNESS, null) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f)) override fun setTemporaryBrightness(value: LinearBrightness) { apiQueue.trySend(SetBrightnessMethod.Temporary(value)) @@ -183,4 +195,21 @@ constructor( @JvmInline value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod } + + private fun logBrightnessChange(permanent: Boolean, value: Float) { + logBuffer.log( + LOG_BUFFER_BRIGHTNESS_CHANGE_TAG, + if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE, + { str1 = value.formatBrightness() }, + { "Change requested: $str1" } + ) + } + + private companion object { + const val TABLE_COLUMN_BRIGHTNESS = "brightness" + const val TABLE_COLUMN_MIN = "min" + const val TABLE_COLUMN_MAX = "max" + const val TABLE_PREFIX_LINEAR = "linear" + const val LOG_BUFFER_BRIGHTNESS_CHANGE_TAG = "BrightnessChange" + } } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt index 799a0a14c99d..5647f521762f 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt @@ -17,12 +17,20 @@ package com.android.systemui.brightness.domain.interactor import com.android.settingslib.display.BrightnessUtils -import com.android.systemui.brightness.data.model.LinearBrightness import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.BrightnessLog +import com.android.systemui.brightness.shared.model.GammaBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness +import com.android.systemui.brightness.shared.model.logDiffForTable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.table.TableLogBuffer import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn /** * Converts between [GammaBrightness] and [LinearBrightness]. @@ -34,6 +42,8 @@ class ScreenBrightnessInteractor @Inject constructor( private val screenBrightnessRepository: ScreenBrightnessRepository, + @Application private val applicationScope: CoroutineScope, + @BrightnessLog private val tableBuffer: TableLogBuffer, ) { /** Maximum value in the Gamma space for brightness */ val maxGammaBrightness = GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX) @@ -45,15 +55,17 @@ constructor( * Brightness in the Gamma space for the current display. It will always represent a value * between [minGammaBrightness] and [maxGammaBrightness] */ - val gammaBrightness = + val gammaBrightness: Flow<GammaBrightness> = with(screenBrightnessRepository) { combine( - linearBrightness, - minLinearBrightness, - maxLinearBrightness, - ) { brightness, min, max -> - brightness.toGammaBrightness(min, max) - } + linearBrightness, + minLinearBrightness, + maxLinearBrightness, + ) { brightness, min, max -> + brightness.toGammaBrightness(min, max) + } + .logDiffForTable(tableBuffer, TABLE_PREFIX_GAMMA, TABLE_COLUMN_BRIGHTNESS, null) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), GammaBrightness(0)) } /** Sets the brightness temporarily, while the user is changing it. */ @@ -91,4 +103,9 @@ constructor( BrightnessUtils.convertLinearToGammaFloat(floatValue, min.floatValue, max.floatValue) ) } + + private companion object { + const val TABLE_COLUMN_BRIGHTNESS = "brightness" + const val TABLE_PREFIX_GAMMA = "gamma" + } } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt new file mode 100644 index 000000000000..b514fefbff0e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.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.brightness.shared.model + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class BrightnessLog() diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt new file mode 100644 index 000000000000..7eba6268869c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.brightness.shared.model + +import androidx.annotation.IntRange +import com.android.settingslib.display.BrightnessUtils +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.util.kotlin.pairwiseBy +import kotlinx.coroutines.flow.Flow + +@JvmInline +value class GammaBrightness( + @IntRange( + from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(), + to = BrightnessUtils.GAMMA_SPACE_MAX.toLong() + ) + val value: Int +) + +internal fun Flow<GammaBrightness>.logDiffForTable( + tableLogBuffer: TableLogBuffer, + columnPrefix: String, + columnName: String, + initialValue: GammaBrightness?, +): Flow<GammaBrightness> { + val initialValueFun = { + tableLogBuffer.logChange(columnPrefix, columnName, initialValue?.value, isInitial = true) + initialValue + } + return this.pairwiseBy(initialValueFun) { prevVal: GammaBrightness?, newVal: GammaBrightness -> + if (prevVal != newVal) { + tableLogBuffer.logChange(columnPrefix, columnName, newVal.value) + } + newVal + } +} diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt new file mode 100644 index 000000000000..1c886e6b1477 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.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.brightness.shared.model + +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.util.kotlin.pairwiseBy +import kotlinx.coroutines.flow.Flow + +@JvmInline +value class LinearBrightness(val floatValue: Float) { + fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness { + return if (floatValue < min.floatValue) { + min + } else if (floatValue > max.floatValue) { + max + } else { + this + } + } + + val loggableString: String + get() = floatValue.formatBrightness() +} + +fun Float.formatBrightness(): String { + return "%.3f".format(this) +} + +internal fun Flow<LinearBrightness>.logDiffForTable( + tableLogBuffer: TableLogBuffer, + columnPrefix: String, + columnName: String, + initialValue: LinearBrightness?, +): Flow<LinearBrightness> { + val initialValueFun = { + tableLogBuffer.logChange( + columnPrefix, + columnName, + initialValue?.loggableString, + isInitial = true + ) + initialValue + } + return this.pairwiseBy(initialValueFun) { prevVal: LinearBrightness?, newVal: LinearBrightness + -> + if (prevVal != newVal) { + tableLogBuffer.logChange(columnPrefix, columnName, newVal.loggableString) + } + newVal + } +} diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt index a51d8ff4faa5..f991d5b8405f 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt @@ -33,14 +33,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSlider -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.GammaBrightness import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.brightness.ui.viewmodel.Drag import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.ui.compose.Icon import com.android.systemui.utils.PolicyRestriction -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @Composable @@ -107,8 +106,8 @@ fun BrightnessSliderContainer( viewModel: BrightnessSliderViewModel, modifier: Modifier = Modifier, ) { - val gamma: Int by - viewModel.currentBrightness.map { it.value }.collectAsStateWithLifecycle(initialValue = 0) + val state by viewModel.currentBrightness.collectAsStateWithLifecycle() + val gamma = state.value val coroutineScope = rememberCoroutineScope() val restriction by viewModel.policyRestriction.collectAsStateWithLifecycle( diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt index f0988ba96bcd..16a1dcc0aef5 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt @@ -18,14 +18,18 @@ package com.android.systemui.brightness.ui.viewmodel import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor -import com.android.systemui.brightness.shared.GammaBrightness +import com.android.systemui.brightness.shared.model.GammaBrightness import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R import com.android.systemui.utils.PolicyRestriction import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn @SysUISingleton class BrightnessSliderViewModel @@ -33,8 +37,14 @@ class BrightnessSliderViewModel constructor( private val screenBrightnessInteractor: ScreenBrightnessInteractor, private val brightnessPolicyEnforcementInteractor: BrightnessPolicyEnforcementInteractor, + @Application private val applicationScope: CoroutineScope, ) { - val currentBrightness = screenBrightnessInteractor.gammaBrightness + val currentBrightness = + screenBrightnessInteractor.gammaBrightness.stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + GammaBrightness(0) + ) val maxBrightness = screenBrightnessInteractor.maxGammaBrightness val minBrightness = screenBrightnessInteractor.minGammaBrightness diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 6f20a8daf00a..d522c7e16603 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -20,6 +20,7 @@ import android.provider.Settings import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton @@ -32,10 +33,13 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.util.kotlin.emitOnStart +import com.android.systemui.util.kotlin.getValue import com.android.systemui.util.kotlin.sample import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import com.android.systemui.util.settings.SystemSettings +import java.util.Optional import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -64,9 +68,11 @@ class CommunalSceneStartable constructor( private val dockManager: DockManager, private val communalInteractor: CommunalInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardInteractor: KeyguardInteractor, private val systemSettings: SystemSettings, + centralSurfacesOpt: Optional<CentralSurfaces>, private val notificationShadeWindowController: NotificationShadeWindowController, @Application private val applicationScope: CoroutineScope, @Background private val bgScope: CoroutineScope, @@ -78,13 +84,15 @@ constructor( private var isDreaming: Boolean = false + private val centralSurfaces: CentralSurfaces? by centralSurfacesOpt + override fun start() { // Handle automatically switching based on keyguard state. keyguardTransitionInteractor.startedKeyguardTransitionStep .mapLatest(::determineSceneAfterTransition) .filterNotNull() .onEach { nextScene -> - communalInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade) + communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade) } .launchIn(applicationScope) @@ -124,7 +132,7 @@ constructor( // app is updated by the Play store, a new timeout should be started. bgScope.launch { combine( - communalInteractor.desiredScene, + communalSceneInteractor.currentScene, // Emit a value on start so the combine starts. communalInteractor.userActivity.emitOnStart() ) { scene, _ -> @@ -140,19 +148,19 @@ constructor( } bgScope.launch { keyguardInteractor.isDreaming - .sample(communalInteractor.desiredScene, ::Pair) + .sample(communalSceneInteractor.currentScene, ::Pair) .collectLatest { (isDreaming, scene) -> this@CommunalSceneStartable.isDreaming = isDreaming if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) { // If dreaming starts after timeout has expired, ex. if dream restarts under // the hub, just close the hub immediately. - communalInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene(CommunalScenes.Blank) } } } bgScope.launch { - communalInteractor.isIdleOnCommunal.collectLatest { + communalSceneInteractor.isIdleOnCommunal.collectLatest { withContext(mainDispatcher) { notificationShadeWindowController.setGlanceableHubShowing(it) } @@ -171,7 +179,7 @@ constructor( bgScope.launch { delay(screenTimeout.milliseconds) if (isDreaming) { - communalInteractor.changeScene(CommunalScenes.Blank) + communalSceneInteractor.changeScene(CommunalScenes.Blank) } timeoutJob = null } @@ -184,11 +192,15 @@ constructor( val to = lastStartedTransition.to val from = lastStartedTransition.from val docked = dockManager.isDocked + val launchingActivityOverLockscreen = + centralSurfaces?.isLaunchingActivityOverLockscreen ?: false return when { - to == KeyguardState.OCCLUDED -> { + to == KeyguardState.OCCLUDED && !launchingActivityOverLockscreen -> { // Hide communal when an activity is started on keyguard, to ensure the activity - // underneath the hub is shown. + // underneath the hub is shown. When launching activities over lockscreen, we only + // change scenes once the activity launch animation is finished, so avoid + // changing the scene here. CommunalScenes.Blank } to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> { 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..260dcbad6201 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 @@ -22,6 +22,7 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.scene.shared.model.SceneDataSource import javax.inject.Inject @@ -34,9 +35,10 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** Encapsulates the state of communal mode. */ -interface CommunalRepository { +interface CommunalSceneRepository { /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. */ @@ -48,6 +50,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 +63,13 @@ interface CommunalRepository { @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton -class CommunalRepositoryImpl +class CommunalSceneRepositoryImpl @Inject constructor( + @Application private val applicationScope: CoroutineScope, @Background backgroundScope: CoroutineScope, @Communal private val sceneDataSource: SceneDataSource, -) : CommunalRepository { +) : CommunalSceneRepository { override val currentScene: StateFlow<SceneKey> = sceneDataSource.currentScene @@ -79,7 +85,19 @@ constructor( ) override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) { - sceneDataSource.changeScene(toScene, transitionKey) + applicationScope.launch { + // SceneTransitionLayout state updates must be triggered on the thread the STL was + // created on. + sceneDataSource.changeScene(toScene, transitionKey) + } + } + + override fun snapToScene(toScene: SceneKey) { + applicationScope.launch { + // SceneTransitionLayout state updates must be triggered on the thread the STL was + // created on. + sceneDataSource.snapToScene(toScene) + } } /** 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/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index db251fde187d..3d9e8615fb18 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -23,6 +23,7 @@ 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.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.media.controls.ui.view.MediaHost @@ -33,10 +34,11 @@ import kotlinx.coroutines.flow.flowOf /** The base view model for the communal hub. */ abstract class BaseCommunalViewModel( + private val communalSceneInteractor: CommunalSceneInteractor, private val communalInteractor: CommunalInteractor, val mediaHost: MediaHost, ) { - val currentScene: Flow<SceneKey> = communalInteractor.desiredScene + val currentScene: Flow<SceneKey> = communalSceneInteractor.currentScene /** Whether communal hub should be focused by accessibility tools. */ open val isFocusable: Flow<Boolean> = MutableStateFlow(false) @@ -58,7 +60,7 @@ abstract class BaseCommunalViewModel( } fun changeScene(scene: SceneKey, transitionKey: TransitionKey? = null) { - communalInteractor.changeScene(scene, transitionKey) + communalSceneInteractor.changeScene(scene, transitionKey) } /** @@ -67,7 +69,7 @@ abstract class BaseCommunalViewModel( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { - communalInteractor.setTransitionState(transitionState) + communalSceneInteractor.setTransitionState(transitionState) } /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 650852ce5876..bc65ccb9ed5a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -25,6 +25,7 @@ import android.util.Log import androidx.activity.result.ActivityResultLauncher import com.android.internal.logging.UiEventLogger import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.log.CommunalUiEvent @@ -50,13 +51,14 @@ import kotlinx.coroutines.withContext class CommunalEditModeViewModel @Inject constructor( + communalSceneInteractor: CommunalSceneInteractor, private val communalInteractor: CommunalInteractor, private val communalSettingsInteractor: CommunalSettingsInteractor, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, private val uiEventLogger: UiEventLogger, @CommunalLog logBuffer: LogBuffer, @Background private val backgroundDispatcher: CoroutineDispatcher, -) : BaseCommunalViewModel(communalInteractor, mediaHost) { +) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) { private val logger = Logger(logBuffer, "CommunalEditModeViewModel") diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 97db43bdf0f8..7f3a2dcb23dc 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -20,6 +20,7 @@ import android.content.res.Resources import android.view.View import android.view.accessibility.AccessibilityNodeInfo import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.dagger.SysUISingleton @@ -65,12 +66,13 @@ constructor( @Main private val resources: Resources, keyguardTransitionInteractor: KeyguardTransitionInteractor, keyguardInteractor: KeyguardInteractor, + communalSceneInteractor: CommunalSceneInteractor, private val communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, private val shadeInteractor: ShadeInteractor, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, @CommunalLog logBuffer: LogBuffer, -) : BaseCommunalViewModel(communalInteractor, mediaHost) { +) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) { private val logger = Logger(logBuffer, "CommunalViewModel") 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/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index 778d8cf56648..51a3a6d827dd 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -61,6 +61,7 @@ constructor( activityStarter.startPendingIntentMaybeDismissingKeyguard( pendingIntent, + /* dismissShade = */ false, /* intentSentUiThreadCallback = */ null, animationController, fillInIntent, diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java index afa23755d937..e284bc7752cf 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java @@ -18,6 +18,7 @@ package com.android.systemui.complication; import static com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW; import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS; +import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE; @@ -35,6 +36,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.Utils; import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent; import com.android.systemui.controls.ControlsServiceInfo; @@ -89,6 +91,7 @@ public class DreamHomeControlsComplication implements Complication { private final DreamHomeControlsComplication mComplication; private final DreamOverlayStateController mDreamOverlayStateController; private final ControlsComponent mControlsComponent; + private final boolean mReplacedByOpenHub; private boolean mOverlayActive = false; @@ -116,11 +119,13 @@ public class DreamHomeControlsComplication implements Complication { public Registrant(DreamHomeControlsComplication complication, DreamOverlayStateController dreamOverlayStateController, ControlsComponent controlsComponent, - @SystemUser Monitor monitor) { + @SystemUser Monitor monitor, + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replacedByOpenHub) { super(monitor); mComplication = complication; mControlsComponent = controlsComponent; mDreamOverlayStateController = dreamOverlayStateController; + mReplacedByOpenHub = replacedByOpenHub; } @Override @@ -132,7 +137,9 @@ public class DreamHomeControlsComplication implements Complication { private void updateHomeControlsComplication() { mControlsComponent.getControlsListingController().ifPresent(c -> { - if (isHomeControlsAvailable(c.getCurrentServices())) { + final boolean replacedWithOpenHub = + Flags.glanceableHubShortcutButton() && mReplacedByOpenHub; + if (isHomeControlsAvailable(c.getCurrentServices()) && !replacedWithOpenHub) { mDreamOverlayStateController.addComplication(mComplication); } else { mDreamOverlayStateController.removeComplication(mComplication); diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java new file mode 100644 index 000000000000..3cf22b1b55e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java @@ -0,0 +1,212 @@ +/* + * 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.complication; + +import static com.android.systemui.complication.dagger.OpenHubComplicationComponent.OpenHubModule.OPEN_HUB_CHIP_VIEW; +import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_LAYOUT_PARAMS; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import com.android.settingslib.Utils; +import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; +import com.android.systemui.communal.domain.interactor.CommunalInteractor; +import com.android.systemui.communal.shared.model.CommunalScenes; +import com.android.systemui.complication.dagger.OpenHubComplicationComponent; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dagger.qualifiers.SystemUser; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.util.ViewController; +import com.android.systemui.util.condition.ConditionalCoreStartable; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * A dream complication that shows a chip to open the glanceable hub. + */ +// TODO(b/339667383): delete or properly implement this once a product decision is made +public class OpenHubComplication implements Complication { + private final Resources mResources; + private final OpenHubComplicationComponent.Factory mComponentFactory; + + @Inject + public OpenHubComplication( + @Main Resources resources, + OpenHubComplicationComponent.Factory componentFactory) { + mResources = resources; + mComponentFactory = componentFactory; + } + + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create(mResources).getViewHolder(); + } + + @Override + public int getRequiredTypeAvailability() { + // TODO(b/339667383): create a new complication type if we decide to productionize this + return COMPLICATION_TYPE_HOME_CONTROLS; + } + + /** + * {@link CoreStartable} for registering the complication with SystemUI on startup. + */ + public static class Registrant extends ConditionalCoreStartable { + private final OpenHubComplication mComplication; + private final DreamOverlayStateController mDreamOverlayStateController; + + private boolean mOverlayActive = false; + + private final DreamOverlayStateController.Callback mOverlayStateCallback = + new DreamOverlayStateController.Callback() { + @Override + public void onStateChanged() { + if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) { + return; + } + + mOverlayActive = !mOverlayActive; + + if (mOverlayActive) { + updateOpenHubComplication(); + } + } + }; + + @Inject + public Registrant(OpenHubComplication complication, + DreamOverlayStateController dreamOverlayStateController, + @SystemUser Monitor monitor) { + super(monitor); + mComplication = complication; + mDreamOverlayStateController = dreamOverlayStateController; + } + + @Override + public void onStart() { + mDreamOverlayStateController.addCallback(mOverlayStateCallback); + } + + private void updateOpenHubComplication() { + // TODO(b/339667383): don't show the complication if glanceable hub is disabled + if (Flags.glanceableHubShortcutButton()) { + mDreamOverlayStateController.addComplication(mComplication); + } else { + mDreamOverlayStateController.removeComplication(mComplication); + } + } + } + + /** + * Contains values/logic associated with the dream complication view. + */ + public static class OpenHubChipViewHolder implements ViewHolder { + private final ImageView mView; + private final ComplicationLayoutParams mLayoutParams; + private final OpenHubChipViewController mViewController; + + @Inject + OpenHubChipViewHolder( + OpenHubChipViewController dreamOpenHubChipViewController, + @Named(OPEN_HUB_CHIP_VIEW) ImageView view, + @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams + ) { + mView = view; + mLayoutParams = layoutParams; + mViewController = dreamOpenHubChipViewController; + mViewController.init(); + } + + @Override + public ImageView getView() { + return mView; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } + } + + /** + * Controls behavior of the dream complication. + */ + static class OpenHubChipViewController extends ViewController<ImageView> { + private static final String TAG = "OpenHubCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + private final ConfigurationController mConfigurationController; + + private final ConfigurationController.ConfigurationListener mConfigurationListener = + new ConfigurationController.ConfigurationListener() { + @Override + public void onUiModeChanged() { + reloadResources(); + } + }; + private final CommunalInteractor mCommunalInteractor; + + @Inject + OpenHubChipViewController( + @Named(OPEN_HUB_CHIP_VIEW) ImageView view, + Context context, + ConfigurationController configurationController, + CommunalInteractor communalInteractor) { + super(view); + + mContext = context; + mConfigurationController = configurationController; + mCommunalInteractor = communalInteractor; + } + + @Override + protected void onViewAttached() { + reloadResources(); + mView.setOnClickListener(this::onClickOpenHub); + mConfigurationController.addCallback(mConfigurationListener); + } + + @Override + protected void onViewDetached() { + mConfigurationController.removeCallback(mConfigurationListener); + } + + private void reloadResources() { + mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + final Drawable background = mView.getBackground(); + if (background != null) { + background.setTintList( + Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface)); + } + } + + private void onClickOpenHub(View v) { + if (DEBUG) Log.d(TAG, "open hub complication tapped"); + + mCommunalInteractor.changeScene(CommunalScenes.Communal, null); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java new file mode 100644 index 000000000000..501601ee32ba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java @@ -0,0 +1,138 @@ +/* + * 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.complication.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.widget.ImageView; + +import com.android.systemui.complication.OpenHubComplication; +import com.android.systemui.res.R; +import com.android.systemui.shared.shadow.DoubleShadowIconDrawable; +import com.android.systemui.shared.shadow.DoubleShadowTextHelper; + +import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +/** + * Responsible for generating dependencies for the {@link OpenHubComplication}. + */ +@Subcomponent(modules = OpenHubComplicationComponent.OpenHubModule.class) +@OpenHubComplicationComponent.OpenHubComplicationScope +public interface OpenHubComplicationComponent { + /** + * Creates a view holder for the open hub complication. + */ + OpenHubComplication.OpenHubChipViewHolder getViewHolder(); + + /** + * Scope of the open hub complication. + */ + @Documented + @Retention(RUNTIME) + @Scope + @interface OpenHubComplicationScope { + } + + /** + * Factory that generates a {@link OpenHubComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + /** + * Creates an instance of {@link OpenHubComplicationComponent}. + */ + OpenHubComplicationComponent create(@BindsInstance Resources resources); + } + + /** + * Scoped injected values for the {@link OpenHubComplicationComponent}. + */ + @Module + interface OpenHubModule { + String OPEN_HUB_CHIP_VIEW = "open_hub_chip_view"; + String OPEN_HUB_BACKGROUND_DRAWABLE = "open_hub_background_drawable"; + + /** + * Provides the dream open hub chip view. + */ + @Provides + @OpenHubComplicationScope + @Named(OPEN_HUB_CHIP_VIEW) + static ImageView provideOpenHubChipView( + LayoutInflater layoutInflater, + @Named(OPEN_HUB_BACKGROUND_DRAWABLE) Drawable backgroundDrawable) { + final ImageView chip = + (ImageView) layoutInflater.inflate(R.layout.dream_overlay_open_hub_chip, + null, false); + chip.setBackground(backgroundDrawable); + + return chip; + } + + /** + * Provides the background drawable for the open hub chip. + */ + @Provides + @OpenHubComplicationScope + @Named(OPEN_HUB_BACKGROUND_DRAWABLE) + static Drawable providesOpenHubBackground(Context context, Resources resources) { + return new DoubleShadowIconDrawable(createShadowInfo( + resources, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dy, + R.dimen.dream_overlay_bottom_affordance_key_shadow_alpha + ), + createShadowInfo( + resources, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_radius, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dx, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dy, + R.dimen.dream_overlay_bottom_affordance_ambient_shadow_alpha + ), + resources.getDrawable(R.drawable.dream_overlay_bottom_affordance_bg), + resources.getDimensionPixelOffset( + R.dimen.dream_overlay_bottom_affordance_width), + resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset) + ); + } + + private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources, + int blurId, int offsetXId, int offsetYId, int alphaId) { + + return new DoubleShadowTextHelper.ShadowInfo( + resources.getDimension(blurId), + resources.getDimension(offsetXId), + resources.getDimension(offsetYId), + resources.getFloat(alphaId) + ); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java index 6f1b09829671..edb5ff7799be 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.util.settings.SystemSettings; import dagger.Module; import dagger.Provides; @@ -39,6 +40,7 @@ import javax.inject.Named; subcomponents = { DreamClockTimeComplicationComponent.class, DreamHomeControlsComplicationComponent.class, + OpenHubComplicationComponent.class, DreamMediaEntryComplicationComponent.class }) public interface RegisteredComplicationsModule { @@ -46,6 +48,8 @@ public interface RegisteredComplicationsModule { String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params"; String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params"; String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params"; + String OPEN_HUB_CHIP_LAYOUT_PARAMS = "open_hub_chip_layout_params"; + String OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS = "open_hub_chip_replace_home_controls"; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE = 2; @@ -109,6 +113,26 @@ public interface RegisteredComplicationsModule { } /** + * Provides layout parameters for the open hub complication. + */ + @Provides + @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) + static ComplicationLayoutParams provideOpenHubLayoutParams( + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replaceHomeControls) { + int position = ComplicationLayoutParams.POSITION_BOTTOM | (replaceHomeControls + ? ComplicationLayoutParams.POSITION_START + : ComplicationLayoutParams.POSITION_END); + int direction = replaceHomeControls ? ComplicationLayoutParams.DIRECTION_END + : ComplicationLayoutParams.DIRECTION_START; + return new ComplicationLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + position, + direction, + DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT); + } + + /** * Provides layout parameters for the smartspace complication. */ @Provides @@ -124,4 +148,14 @@ public interface RegisteredComplicationsModule { res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding), res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_max_width)); } + + /** + * If true, the home controls chip should not be shown and the open hub chip should be shown in + * its place. + */ + @Provides + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) + static boolean providesOpenHubChipReplaceHomeControls(SystemSettings systemSettings) { + return systemSettings.getBool(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS, false); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 3e98fc1b4a2a..7aab37e12b8c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -32,6 +32,8 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.doze.DozeHost; import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule; +import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule; import com.android.systemui.media.dagger.MediaModule; import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; @@ -112,6 +114,8 @@ import javax.inject.Named; GestureModule.class, HeadsUpModule.class, KeyboardShortcutsModule.class, + KeyguardBlueprintModule.class, + KeyguardSectionsModule.class, MediaModule.class, MediaMuteAwaitConnectionCli.StartableModule.class, MultiUserUtilsModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 93f37937b7ce..2ebb94f8bcf4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -70,12 +70,11 @@ import com.android.systemui.inputmethod.InputMethodModule; import com.android.systemui.keyboard.KeyboardModule; import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; import com.android.systemui.keyguard.ui.composable.LockscreenContent; -import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; -import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; -import com.android.systemui.mediaprojection.appselector.MediaProjectionModule; +import com.android.systemui.mediaprojection.MediaProjectionModule; +import com.android.systemui.mediaprojection.appselector.MediaProjectionActivitiesModule; import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule; import com.android.systemui.model.SceneContainerPlugin; import com.android.systemui.model.SysUiState; @@ -221,10 +220,9 @@ import javax.inject.Named; InputMethodModule.class, KeyEventRepositoryModule.class, KeyboardModule.class, - KeyguardBlueprintModule.class, - KeyguardSectionsModule.class, LetterboxModule.class, LogModule.class, + MediaProjectionActivitiesModule.class, MediaProjectionModule.class, MediaProjectionTaskSwitcherModule.class, MotionToolModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt index 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/dock/DockManagerExtensions.kt b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt index 4dbb32da62c2..1bbdfcd88548 100644 --- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt +++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt @@ -19,16 +19,18 @@ package com.android.systemui.dock import com.android.systemui.common.coroutine.ConflatedCallbackFlow import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged /** - * Retrieves whether or not the device is docked according to DockManager. Emits a starting value - * of isDocked. + * Retrieves whether or not the device is docked according to DockManager. Emits a starting value of + * isDocked. */ fun DockManager.retrieveIsDocked(): Flow<Boolean> = ConflatedCallbackFlow.conflatedCallbackFlow { - val callback = DockManager.DockEventListener { trySend(isDocked) } - addListener(callback) - trySend(isDocked) + val callback = DockManager.DockEventListener { trySend(isDocked) } + addListener(callback) + trySend(isDocked) - awaitClose { removeListener(callback) } - }
\ No newline at end of file + awaitClose { removeListener(callback) } + } + .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index aa7a7dae8f5e..96e708fca451 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -543,7 +543,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mStateController.setEntryAnimationsFinished(false); mDreamOverlayContainerViewController = null; - mTouchMonitor = null; + + if (mTouchMonitor != null) { + mTouchMonitor.destroy(); + mTouchMonitor = null; + } mWindow = null; mStarted = false; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index b0d134f5f15f..f6ac7a579140 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -33,8 +33,8 @@ import com.android.systemui.dreams.DreamOverlayNotificationCountProvider; import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dreams.SystemDialogsCloser; import com.android.systemui.dreams.complication.dagger.ComplicationComponent; -import com.android.systemui.dreams.homecontrols.DreamActivityProvider; -import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl; +import com.android.systemui.dreams.homecontrols.DreamServiceDelegate; +import com.android.systemui.dreams.homecontrols.DreamServiceDelegateImpl; import com.android.systemui.dreams.homecontrols.HomeControlsDreamService; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.pipeline.shared.TileSpec; @@ -202,8 +202,8 @@ public interface DreamModule { } - /** Provides activity for dream service */ + /** Provides delegate to allow for testing of dream service */ @Binds - DreamActivityProvider bindActivityProvider(DreamActivityProviderImpl impl); + DreamServiceDelegate bindDreamDelegate(DreamServiceDelegateImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt new file mode 100644 index 000000000000..2cfb02eadd19 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt @@ -0,0 +1,34 @@ +/* + * 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.dreams.homecontrols + +import android.app.Activity +import android.service.dreams.DreamService + +/** Provides abstraction for [DreamService] methods, so they can be mocked in tests. */ +interface DreamServiceDelegate { + /** Wrapper for [DreamService.getActivity] which can be mocked in tests. */ + fun getActivity(dreamService: DreamService): Activity? + + /** Wrapper for [DreamService.wakeUp] which can be mocked in tests. */ + fun wakeUp(dreamService: DreamService) + + /** Wrapper for [DreamService.finish] which can be mocked in tests. */ + fun finish(dreamService: DreamService) + + /** Wrapper for [DreamService.getRedirectWake] which can be mocked in tests. */ + fun redirectWake(dreamService: DreamService): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt index 0854e939645b..7dc5434c595e 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt @@ -19,8 +19,20 @@ import android.app.Activity import android.service.dreams.DreamService import javax.inject.Inject -class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider { +class DreamServiceDelegateImpl @Inject constructor() : DreamServiceDelegate { override fun getActivity(dreamService: DreamService): Activity { return dreamService.activity } + + override fun finish(dreamService: DreamService) { + dreamService.finish() + } + + override fun wakeUp(dreamService: DreamService) { + dreamService.wakeUp() + } + + override fun redirectWake(dreamService: DreamService): Boolean { + return dreamService.redirectWake + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt index 76187c614b5d..77c54ec1eac3 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt @@ -31,6 +31,7 @@ import com.android.systemui.log.dagger.DreamLog import com.android.systemui.util.wakelock.WakeLock import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -46,7 +47,7 @@ constructor( private val taskFragmentFactory: TaskFragmentComponent.Factory, private val homeControlsComponentInteractor: HomeControlsComponentInteractor, private val wakeLockBuilder: WakeLock.Builder, - private val dreamActivityProvider: DreamActivityProvider, + private val dreamServiceDelegate: DreamServiceDelegate, @Background private val bgDispatcher: CoroutineDispatcher, @DreamLog logBuffer: LogBuffer ) : DreamService() { @@ -65,7 +66,7 @@ constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - val activity = dreamActivityProvider.getActivity(this) + val activity = dreamServiceDelegate.getActivity(this) if (activity == null) { finish() return @@ -79,9 +80,9 @@ constructor( taskFragmentFactory .create( activity = activity, - onCreateCallback = this::onTaskFragmentCreated, + onCreateCallback = { launchActivity() }, onInfoChangedCallback = this::onTaskFragmentInfoChanged, - hide = { endDream() } + hide = { endDream(false) } ) .apply { createTaskFragment() } @@ -91,16 +92,24 @@ constructor( private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) { if (taskFragmentInfo.isEmpty) { logger.d("Finishing dream due to TaskFragment being empty") - endDream() + endDream(true) } } - private fun endDream() { + private fun endDream(handleRedirect: Boolean) { homeControlsComponentInteractor.onDreamEndUnexpectedly() - finish() + if (handleRedirect && dreamServiceDelegate.redirectWake(this)) { + dreamServiceDelegate.wakeUp(this) + serviceScope.launch { + delay(ACTIVITY_RESTART_DELAY) + launchActivity() + } + } else { + dreamServiceDelegate.finish(this) + } } - private fun onTaskFragmentCreated(taskFragmentInfo: TaskFragmentInfo) { + private fun launchActivity() { val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value val componentName = homeControlsComponentInteractor.panelComponent.value logger.d("Starting embedding $componentName") @@ -134,6 +143,14 @@ constructor( * complete. */ val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds + + /** + * Defines the delay after wakeup where we should attempt to restart the embedded activity. + * When a wakeup is redirected, the dream service may keep running. In this case, we should + * restart the activity if it finished. This delays ensures the activity is only restarted + * after the wakeup transition has played. + */ + val ACTIVITY_RESTART_DELAY = 334.milliseconds const val TAG = "HomeControlsDreamService" } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java index 1c047ddcd3d8..04fda3313df6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java @@ -98,7 +98,7 @@ public class CommunalTouchHandler implements TouchHandler { // Notification shade window has its own logic to be visible if the hub is open, no need to // do anything here other than send touch events over. session.registerInputListener(ev -> { - surfaces.handleDreamTouch((MotionEvent) ev); + surfaces.handleCommunalHubTouch((MotionEvent) ev); if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) { var unused = session.pop(); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 2e49919d489b..f4f8796ebffc 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -42,14 +42,6 @@ object Flags { @JvmField val NULL_FLAG = unreleasedFlag("null_flag") // 100 - notification - // TODO(b/297792660): Tracking Bug - @JvmField val UNCLEARED_TRANSIENT_HUN_FIX = - releasedFlag("uncleared_transient_hun_fix") - - // TODO(b/298308067): Tracking Bug - @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX = - releasedFlag("swipe_uncleared_transient_view_fix") - // TODO(b/254512751): Tracking Bug val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = unreleasedFlag("notification_pipeline_developer_logging") @@ -170,12 +162,6 @@ object Flags { val WALLPAPER_PICKER_GRID_APPLY_BUTTON = unreleasedFlag("wallpaper_picker_grid_apply_button") - /** Keyguard Migration */ - - // TODO(b/297037052): Tracking bug. - @JvmField - val REMOVE_NPVC_BOTTOM_AREA_USAGE = unreleasedFlag("remove_npvc_bottom_area_usage") - /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */ // TODO(b/286563884): Tracking bug @JvmField val KEYGUARD_TALKBACK_FIX = unreleasedFlag("keyguard_talkback_fix") @@ -473,14 +459,6 @@ object Flags { @JvmField val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation") - /** Enable the Compose implementation of the PeopleSpaceActivity. */ - @JvmField - val COMPOSE_PEOPLE_SPACE = releasedFlag("compose_people_space") - - /** Enable the Compose implementation of the Quick Settings footer actions. */ - @JvmField - val COMPOSE_QS_FOOTER_ACTIONS = releasedFlag("compose_qs_footer_actions") - /** Enable the share wifi button in Quick Settings internet dialog. */ @JvmField val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button") 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/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index c32c226441fe..306f4ffa2a54 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -53,7 +53,6 @@ import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.view.KeyguardRootView -import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel @@ -89,7 +88,6 @@ constructor( private val screenOffAnimationController: ScreenOffAnimationController, private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, private val chipbarCoordinator: ChipbarCoordinator, - private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener, private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel, private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val configuration: ConfigurationState, @@ -105,7 +103,6 @@ constructor( private val smartspaceViewModel: KeyguardSmartspaceViewModel, private val lockscreenContentViewModel: LockscreenContentViewModel, private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>, - private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder, private val clockInteractor: KeyguardClockInteractor, private val keyguardViewMediator: KeyguardViewMediator, ) : CoreStartable { @@ -152,7 +149,7 @@ constructor( cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM) keyguardRootView.addView(composeView) } else { - keyguardBlueprintViewBinder.bind( + KeyguardBlueprintViewBinder.bind( keyguardRootView, keyguardBlueprintViewModel, keyguardClockViewModel, @@ -160,7 +157,6 @@ constructor( ) } } - keyguardBlueprintCommandListener.start() } fun bindIndicationArea() { @@ -200,12 +196,14 @@ constructor( KeyguardRootViewBinder.bind( keyguardRootView, keyguardRootViewModel, + keyguardBlueprintViewModel, configuration, occludingAppDeviceEntryMessageViewModel, chipbarCoordinator, screenOffAnimationController, shadeInteractor, clockInteractor, + keyguardClockViewModel, interactionJankMonitor, deviceEntryHapticsInteractor, vibratorHelper, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index 80675d373b8e..0863cd737529 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -28,6 +28,8 @@ object BuiltInKeyguardQuickAffordanceKeys { const val CREATE_NOTE = "create_note" const val DO_NOT_DISTURB = "do_not_disturb" const val FLASHLIGHT = "flashlight" + // TODO(b/339667383): delete or properly implement this once a product decision is made + const val GLANCEABLE_HUB = "glanceable_hub" const val HOME_CONTROLS = "home" const val MUTE = "mute" const val QR_CODE_SCANNER = "qr_code_scanner" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt new file mode 100644 index 000000000000..d09b9f68ea60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.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.keyguard.data.quickaffordance + +import com.android.systemui.Flags +import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.communal.data.repository.CommunalSceneRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** Shortcut that opens the glanceable hub. */ +// TODO(b/339667383): delete or properly implement this once a product decision is made +@SysUISingleton +class GlanceableHubQuickAffordanceConfig +@Inject +constructor( + private val communalRepository: CommunalSceneRepository, +) : KeyguardQuickAffordanceConfig { + + override val key: String = BuiltInKeyguardQuickAffordanceKeys.GLANCEABLE_HUB + override fun pickerName(): String = "Glanceable hub" + + override val pickerIconResourceId = R.drawable.ic_widgets + + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> by lazy { + if (Flags.glanceableHubShortcutButton()) { + val contentDescription = ContentDescription.Loaded(pickerName()) + val icon = Icon.Resource(pickerIconResourceId, contentDescription) + flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon)) + } else { + flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } + } + + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + communalRepository.changeScene(CommunalScenes.Communal, null) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index 45561959a7df..93296f0ca24b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -36,6 +36,7 @@ interface KeyguardDataQuickAffordanceModule { camera: CameraQuickAffordanceConfig, doNotDisturb: DoNotDisturbQuickAffordanceConfig, flashlight: FlashlightQuickAffordanceConfig, + glanceableHub: GlanceableHubQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, mute: MuteQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, @@ -46,6 +47,7 @@ interface KeyguardDataQuickAffordanceModule { camera, doNotDisturb, flashlight, + glanceableHub, home, mute, quickAccessWallet, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt index deedbdb9e72b..0748979a2465 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt @@ -20,15 +20,17 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context import android.content.IntentFilter import android.content.SharedPreferences -import com.android.systemui.res.R +import com.android.systemui.Flags import com.android.systemui.backup.BackupHelper import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.util.settings.SystemSettings import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -50,6 +52,7 @@ constructor( @Application private val context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, + private val systemSettings: SystemSettings, broadcastDispatcher: BroadcastDispatcher, ) : KeyguardQuickAffordanceSelectionManager { @@ -70,6 +73,22 @@ constructor( } private val defaults: Map<String, List<String>> by lazy { + // Quick hack to allow testing out a lock screen shortcut to open the glanceable hub. This + // flag will not be rolled out and is only used for local testing. + // TODO(b/339667383): delete or properly implement this once a product decision is made + if (Flags.glanceableHubShortcutButton()) { + if (systemSettings.getBool("open_hub_chip_replace_home_controls", false)) { + return@lazy mapOf( + "bottom_start" to listOf("glanceable_hub"), + "bottom_end" to listOf("create_note") + ) + } else { + return@lazy mapOf( + "bottom_start" to listOf("home"), + "bottom_end" to listOf("glanceable_hub") + ) + } + } context.resources .getStringArray(R.array.config_keyguardQuickAffordanceDefaults) .associate { item -> 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/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt index c11c49c7a8a0..b826a002b9d9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt @@ -91,9 +91,9 @@ constructor( */ fun refreshBlueprint(config: Config = Config.DEFAULT) { fun scheduleCallback() { - // We use a handler here instead of a CoroutineDipsatcher because the one provided by + // We use a handler here instead of a CoroutineDispatcher because the one provided by // @Main CoroutineDispatcher is currently Dispatchers.Main.immediate, which doesn't - // delay the callback, and instead runs it imemdiately. + // delay the callback, and instead runs it immediately. handler.post { assert.isMainThread() targetTransitionConfig?.let { 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/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 9b07675f672c..756c6c20e58d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -57,7 +57,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, private val communalInteractor: CommunalInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -70,6 +70,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { 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..2a9ee9fb8779 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 @@ -47,9 +48,10 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + val deviceEntryRepository: DeviceEntryRepository, ) : TransitionInteractor( fromState = KeyguardState.AOD, @@ -58,6 +60,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { @@ -125,7 +128,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..f5e98f1fedfe 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 @@ -46,10 +47,11 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + val deviceEntryRepository: DeviceEntryRepository, ) : TransitionInteractor( fromState = KeyguardState.DOZING, @@ -58,6 +60,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { @@ -99,7 +102,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 +150,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 +163,8 @@ constructor( KeyguardState.GLANCEABLE_HUB } else { KeyguardState.LOCKSCREEN - } + }, + ownerReason = "waking from dozing" ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt index 63294f7609a2..47aa02a0be52 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt @@ -45,7 +45,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : @@ -56,6 +56,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 7961b45830d4..25c3b0d395c0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -51,7 +51,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, private val glanceableHubTransitions: GlanceableHubTransitions, powerInteractor: PowerInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -63,6 +63,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index ca6ab3ef52d8..e516fa3c44bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -48,7 +48,7 @@ constructor( @Main mainDispatcher: CoroutineDispatcher, @Background bgDispatcher: CoroutineDispatcher, private val glanceableHubTransitions: GlanceableHubTransitions, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, override val transitionRepository: KeyguardTransitionRepository, transitionInteractor: KeyguardTransitionInteractor, powerInteractor: PowerInteractor, @@ -61,6 +61,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { 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..a540d761c38f 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 @@ -47,11 +49,13 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, private val biometricSettingsRepository: BiometricSettingsRepository, + private val keyguardRepository: KeyguardRepository, + private val keyguardEnabledInteractor: KeyguardEnabledInteractor, ) : TransitionInteractor( fromState = KeyguardState.GONE, @@ -60,6 +64,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { @@ -93,6 +98,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/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index f1e98f3bbe6d..8cab3cd35dcf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -58,7 +58,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, private val flags: FeatureFlags, private val shadeRepository: ShadeRepository, powerInteractor: PowerInteractor, @@ -73,6 +73,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index 2603aab2781b..86d4cfb916ed 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -45,7 +45,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, @@ -57,6 +57,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 53a0c3200f3d..19b2b81c4b27 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -52,7 +52,7 @@ constructor( @Background private val scope: CoroutineScope, @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, - private val keyguardInteractor: KeyguardInteractor, + keyguardInteractor: KeyguardInteractor, private val communalInteractor: CommunalInteractor, private val flags: FeatureFlags, private val keyguardSecurityModel: KeyguardSecurityModel, @@ -67,6 +67,7 @@ constructor( bgDispatcher = bgDispatcher, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + keyguardInteractor = keyguardInteractor, ) { override fun start() { @@ -86,7 +87,7 @@ constructor( return@combine null } - fromBouncerStep.value > 0.5f + fromBouncerStep.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD } .onStart { // Default to null ("don't care, use a reasonable default"). @@ -232,5 +233,6 @@ constructor( val TO_AOD_DURATION = DEFAULT_DURATION val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION val TO_DOZING_DURATION = DEFAULT_DURATION + val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.5f } } 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/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index 7cee258dc39f..41c39597dc02 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context +import com.android.systemui.CoreStartable import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton @@ -31,17 +32,17 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBl import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type +import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection +import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch @SysUISingleton @@ -53,9 +54,11 @@ constructor( private val context: Context, private val shadeInteractor: ShadeInteractor, private val clockInteractor: KeyguardClockInteractor, - configurationInteractor: ConfigurationInteractor, - fingerprintPropertyInteractor: FingerprintPropertyInteractor, -) { + private val configurationInteractor: ConfigurationInteractor, + private val fingerprintPropertyInteractor: FingerprintPropertyInteractor, + private val smartspaceSection: SmartspaceSection, + private val clockSection: ClockSection, +) : CoreStartable { /** The current blueprint for the lockscreen. */ val blueprint: StateFlow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint @@ -75,15 +78,23 @@ constructor( } } - private val refreshEvents: Flow<Unit> = - merge( - configurationInteractor.onAnyConfigurationChange, - fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map {}, - ) - - init { + override fun start() { applicationScope.launch { blueprintId.collect { transitionToBlueprint(it) } } - applicationScope.launch { refreshEvents.collect { refreshBlueprint() } } + applicationScope.launch { + fingerprintPropertyInteractor.propertiesInitialized + .filter { it } + .collect { refreshBlueprint() } + } + applicationScope.launch { + val refreshConfig = + Config( + Type.NoTransition, + rebuildSections = listOf(smartspaceSection), + ) + configurationInteractor.onAnyConfigurationChange.collect { + refreshBlueprint(refreshConfig) + } + } } /** @@ -120,4 +131,8 @@ constructor( fun getCurrentBlueprint(): KeyguardBlueprint { return keyguardBlueprintRepository.blueprint.value } + + companion object { + private val TAG = "KeyguardBlueprintInteractor" + } } 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/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index ccce3bf1397c..8ffa4bb8e4fb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -105,33 +105,35 @@ constructor( } return combine( - quickAffordanceAlwaysVisible(position), - keyguardInteractor.isDozing, - if (SceneContainerFlag.isEnabled) { - sceneInteractor - .get() - .transitionState - .map { - when (it) { - is ObservableTransitionState.Idle -> - it.currentScene == Scenes.Lockscreen - is ObservableTransitionState.Transition -> - it.fromScene == Scenes.Lockscreen || it.toScene == Scenes.Lockscreen + quickAffordanceAlwaysVisible(position), + keyguardInteractor.isDozing, + if (SceneContainerFlag.isEnabled) { + sceneInteractor + .get() + .transitionState + .map { + when (it) { + is ObservableTransitionState.Idle -> + it.currentScene == Scenes.Lockscreen + is ObservableTransitionState.Transition -> + it.fromScene == Scenes.Lockscreen || + it.toScene == Scenes.Lockscreen + } } - } - .distinctUntilChanged() - } else { - keyguardInteractor.isKeyguardShowing - }, - shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(), - biometricSettingsRepository.isCurrentUserInLockdown, - ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown -> - if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) { - affordance - } else { - KeyguardQuickAffordanceModel.Hidden + .distinctUntilChanged() + } else { + keyguardInteractor.isKeyguardShowing + }, + shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(), + biometricSettingsRepository.isCurrentUserInLockdown, + ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown -> + if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) { + affordance + } else { + KeyguardQuickAffordanceModel.Hidden + } } - } + .distinctUntilChanged() } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index 323ceef06a97..e14820714c9b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -53,6 +53,7 @@ sealed class TransitionInteractor( val bgDispatcher: CoroutineDispatcher, val powerInteractor: PowerInteractor, val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + val keyguardInteractor: KeyguardInteractor, ) { val name = this::class.simpleName ?: "UnknownTransitionInteractor" abstract val transitionRepository: KeyguardTransitionRepository @@ -164,14 +165,10 @@ sealed class TransitionInteractor( @Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera") suspend fun maybeHandleInsecurePowerGesture(): Boolean { if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) { - if (transitionInteractor.getCurrentState() == KeyguardState.GONE) { - // If the current state is GONE when the launch gesture is triggered, it means we - // were in transition from GONE -> DOZING/AOD due to the first power button tap. The - // second tap indicates that the user's intent was actually to launch the unlocked - // (insecure) camera, so we should transition back to GONE. + if (keyguardInteractor.isKeyguardDismissible.value) { startTransitionTo( KeyguardState.GONE, - ownerReason = "Power button gesture while GONE" + ownerReason = "Power button gesture while keyguard is dismissible" ) return true 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..88e6602e56b7 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 @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.shared.model.BiometricUnlockMode @@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.util.kotlin.sample +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -84,25 +86,52 @@ constructor( } .distinctUntilChanged() + private val isDeviceEntered: Flow<Boolean> by lazy { + deviceEntryInteractor.get().isDeviceEntered + } + + private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } } + /** - * Surface visibility, which is either determined by the default visibility in the FINISHED - * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions. + * Surface visibility, which is either determined by the default visibility when not + * transitioning between [KeyguardState]s or [Scenes] or the transition-specific visibility used + * during certain ongoing transitions. */ @OptIn(ExperimentalCoroutinesApi::class) val surfaceBehindVisibility: Flow<Boolean> = - transitionInteractor.isInTransitionToAnyState - .flatMapLatest { isInTransition -> - if (!isInTransition) { - defaultSurfaceBehindVisibility - } else { - combine( - transitionSpecificSurfaceBehindVisibility, - defaultSurfaceBehindVisibility, - ) { transitionVisibility, defaultVisibility -> - // Defer to the transition-specific visibility since we're RUNNING a - // transition, but fall back to the default visibility if the current - // transition's interactor did not specify a visibility. - transitionVisibility ?: defaultVisibility + if (SceneContainerFlag.isEnabled) { + sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState -> + when (transitionState) { + is ObservableTransitionState.Transition -> + when { + transitionState.fromScene == Scenes.Lockscreen && + transitionState.toScene == Scenes.Gone -> flowOf(true) + transitionState.fromScene == Scenes.Bouncer && + transitionState.toScene == Scenes.Gone -> + transitionState.progress.map { progress -> + progress > + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + } + else -> isDeviceEntered + } + is ObservableTransitionState.Idle -> isDeviceEntered + } + } + } else { + transitionInteractor.isInTransitionToAnyState.flatMapLatest { isInTransition -> + if (!isInTransition) { + defaultSurfaceBehindVisibility + } else { + combine( + transitionSpecificSurfaceBehindVisibility, + defaultSurfaceBehindVisibility, + ) { transitionVisibility, defaultVisibility -> + // Defer to the transition-specific visibility since we're RUNNING a + // transition, but fall back to the default visibility if the current + // transition's interactor did not specify a visibility. + transitionVisibility ?: defaultVisibility + } } } } @@ -162,7 +191,7 @@ constructor( */ val lockscreenVisibility: Flow<Boolean> = if (SceneContainerFlag.isEnabled) { - deviceEntryInteractor.get().isDeviceEntered.map { !it } + isDeviceNotEntered } else { transitionInteractor.currentKeyguardState .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) @@ -176,7 +205,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/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt index 7ca2ebaeab20..6d579f3b2513 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt @@ -37,16 +37,32 @@ interface KeyguardBlueprint { fun replaceViews( constraintLayout: ConstraintLayout, previousBlueprint: KeyguardBlueprint? = null, + rebuildSections: List<KeyguardSection> = listOf(), bindData: Boolean = true ) { - val prevSections = - previousBlueprint?.let { prev -> - prev.sections.subtract(sections).forEach { it.removeViews(constraintLayout) } - prev.sections + val prevSections = previousBlueprint?.sections ?: listOf() + val skipSections = sections.intersect(prevSections).subtract(rebuildSections) + prevSections.subtract(skipSections).forEach { it.removeViews(constraintLayout) } + sections.subtract(skipSections).forEach { + it.addViews(constraintLayout) + if (bindData) { + it.bindData(constraintLayout) } - ?: listOf() + } + } + + /** Rebuilds views for the target sections, or all of them if unspecified. */ + fun rebuildViews( + constraintLayout: ConstraintLayout, + rebuildSections: List<KeyguardSection> = sections, + bindData: Boolean = true + ) { + if (rebuildSections.isEmpty()) { + return + } - sections.subtract(prevSections).forEach { + rebuildSections.forEach { it.removeViews(constraintLayout) } + rebuildSections.forEach { it.addViews(constraintLayout) if (bindData) { it.bindData(constraintLayout) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 52d7519c2e3c..bec8f3da9999 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -17,17 +17,12 @@ package com.android.systemui.keyguard.ui.binder -import android.os.Handler -import android.transition.Transition -import android.transition.TransitionManager import android.util.Log import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launch -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition @@ -40,47 +35,9 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import com.android.systemui.util.kotlin.pairwise -import javax.inject.Inject -import kotlin.math.max - -@SysUISingleton -class KeyguardBlueprintViewBinder -@Inject -constructor( - @Main private val handler: Handler, -) { - private var runningPriority = -1 - private val runningTransitions = mutableSetOf<Transition>() - private val isTransitionRunning: Boolean - get() = runningTransitions.size > 0 - private val transitionListener = - object : Transition.TransitionListener { - override fun onTransitionCancel(transition: Transition) { - if (DEBUG) Log.e(TAG, "onTransitionCancel: ${transition::class.simpleName}") - runningTransitions.remove(transition) - } - - override fun onTransitionEnd(transition: Transition) { - if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}") - runningTransitions.remove(transition) - } - - override fun onTransitionPause(transition: Transition) { - if (DEBUG) Log.i(TAG, "onTransitionPause: ${transition::class.simpleName}") - runningTransitions.remove(transition) - } - - override fun onTransitionResume(transition: Transition) { - if (DEBUG) Log.i(TAG, "onTransitionResume: ${transition::class.simpleName}") - runningTransitions.add(transition) - } - - override fun onTransitionStart(transition: Transition) { - if (DEBUG) Log.i(TAG, "onTransitionStart: ${transition::class.simpleName}") - runningTransitions.add(transition) - } - } +object KeyguardBlueprintViewBinder { + @JvmStatic fun bind( constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel, @@ -95,17 +52,8 @@ constructor( null as KeyguardBlueprint?, ) .collect { (prevBlueprint, blueprint) -> - val cs = - ConstraintSet().apply { - clone(constraintLayout) - val emptyLayout = ConstraintSet.Layout() - knownIds.forEach { - getConstraint(it).layout.copyFrom(emptyLayout) - } - blueprint.applyConstraints(this) - } - - var transition = + val config = Config.DEFAULT + val transition = if ( !KeyguardBottomAreaRefactor.isEnabled && prevBlueprint != null && @@ -114,23 +62,37 @@ constructor( BaseBlueprintTransition(clockViewModel) .addTransition( IntraBlueprintTransition( - Config.DEFAULT, + config, clockViewModel, smartspaceViewModel ) ) } else { IntraBlueprintTransition( - Config.DEFAULT, + config, clockViewModel, smartspaceViewModel ) } - runTransition(constraintLayout, transition, Config.DEFAULT) { - // Add and remove views of sections that are not contained by the - // other. - blueprint.replaceViews(constraintLayout, prevBlueprint) + viewModel.runTransition(constraintLayout, transition, config) { + // Replace sections from the previous blueprint with the new ones + blueprint.replaceViews( + constraintLayout, + prevBlueprint, + config.rebuildSections + ) + + val cs = + ConstraintSet().apply { + clone(constraintLayout) + val emptyLayout = ConstraintSet.Layout() + knownIds.forEach { + getConstraint(it).layout.copyFrom(emptyLayout) + } + blueprint.applyConstraints(this) + } + logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel) cs.applyTo(constraintLayout) } @@ -138,22 +100,21 @@ constructor( } launch("$TAG#viewModel.refreshTransition") { - viewModel.refreshTransition.collect { transition -> - val cs = - ConstraintSet().apply { - clone(constraintLayout) - viewModel.blueprint.value.applyConstraints(this) - } + viewModel.refreshTransition.collect { config -> + val blueprint = viewModel.blueprint.value - runTransition( + viewModel.runTransition( constraintLayout, - IntraBlueprintTransition( - transition, - clockViewModel, - smartspaceViewModel - ), - transition, + IntraBlueprintTransition(config, clockViewModel, smartspaceViewModel), + config, ) { + blueprint.rebuildViews(constraintLayout, config.rebuildSections) + + val cs = + ConstraintSet().apply { + clone(constraintLayout) + blueprint.applyConstraints(this) + } logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel) cs.applyTo(constraintLayout) } @@ -163,50 +124,6 @@ constructor( } } - private fun runTransition( - constraintLayout: ConstraintLayout, - transition: Transition, - config: Config, - apply: () -> Unit, - ) { - val currentPriority = if (isTransitionRunning) runningPriority else -1 - if (config.checkPriority && config.type.priority < currentPriority) { - if (DEBUG) { - Log.w( - TAG, - "runTransition: skipping ${transition::class.simpleName}: " + - "currentPriority=$currentPriority; config=$config" - ) - } - apply() - return - } - - if (DEBUG) { - Log.i( - TAG, - "runTransition: running ${transition::class.simpleName}: " + - "currentPriority=$currentPriority; config=$config" - ) - } - - // beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to - // the running set until the copy is started by the handler. - runningTransitions.add(transition) - transition.addListener(transitionListener) - runningPriority = max(currentPriority, config.type.priority) - - handler.post { - if (config.terminatePrevious) { - TransitionManager.endTransitions(constraintLayout) - } - - TransitionManager.beginDelayedTransition(constraintLayout, transition) - runningTransitions.remove(transition) - apply() - } - } - private fun logAlphaVisibilityOfAppliedConstraintSet( cs: ConstraintSet, viewModel: KeyguardClockViewModel @@ -233,8 +150,6 @@ constructor( ) } - companion object { - private const val TAG = "KeyguardBlueprintViewBinder" - private const val DEBUG = false - } + private const val TAG = "KeyguardBlueprintViewBinder" + private const val DEBUG = false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 23c2491813f7..807c322cc566 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -65,7 +65,7 @@ object KeyguardIndicationAreaBinder { val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val disposableHandle = view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { + repeatOnLifecycle(Lifecycle.State.CREATED) { launch("$TAG#viewModel.alpha") { // Do not independently apply alpha, as [KeyguardRootViewModel] should work // for this and all its children diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index b9a79dccf76b..1cf009d13e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -30,6 +30,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.app.tracing.coroutines.launch import com.android.settingslib.Utils import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableImageView @@ -80,8 +81,8 @@ object KeyguardQuickAffordanceViewBinder { val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val disposableHandle = view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch("$TAG#viewModel.collect") { viewModel.collect { buttonModel -> updateButton( view = button, @@ -93,7 +94,7 @@ object KeyguardQuickAffordanceViewBinder { } } - launch { + launch("$TAG#updateButtonAlpha") { updateButtonAlpha( view = button, viewModel = viewModel, @@ -101,7 +102,7 @@ object KeyguardQuickAffordanceViewBinder { ) } - launch { + launch("$TAG#configurationBasedDimensions") { configurationBasedDimensions.collect { dimensions -> button.updateLayoutParams<ViewGroup.LayoutParams> { width = dimensions.buttonSizePx.width @@ -323,4 +324,6 @@ object KeyguardQuickAffordanceViewBinder { private data class ConfigurationBasedDimensions( val buttonSizePx: Size, ) + + private const val TAG = "KeyguardQuickAffordanceViewBinder" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 39db22d5616d..fc92afe17eff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -22,6 +22,7 @@ import android.annotation.DrawableRes import android.annotation.SuppressLint import android.graphics.Point import android.graphics.Rect +import android.util.Log import android.view.HapticFeedbackConstants import android.view.View import android.view.View.OnLayoutChangeListener @@ -56,8 +57,11 @@ import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel +import com.android.systemui.keyguard.ui.viewmodel.TransitionData import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager @@ -93,12 +97,14 @@ object KeyguardRootViewBinder { fun bind( view: ViewGroup, viewModel: KeyguardRootViewModel, + blueprintViewModel: KeyguardBlueprintViewModel, configuration: ConfigurationState, occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel?, chipbarCoordinator: ChipbarCoordinator?, screenOffAnimationController: ScreenOffAnimationController, shadeInteractor: ShadeInteractor, clockInteractor: KeyguardClockInteractor, + clockViewModel: KeyguardClockViewModel, interactionJankMonitor: InteractionJankMonitor?, deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, vibratorHelper: VibratorHelper?, @@ -348,7 +354,16 @@ object KeyguardRootViewBinder { } } - disposables += view.onLayoutChanged(OnLayoutChange(viewModel, childViews, burnInParams)) + disposables += + view.onLayoutChanged( + OnLayoutChange( + viewModel, + blueprintViewModel, + clockViewModel, + childViews, + burnInParams + ) + ) // Views will be added or removed after the call to bind(). This is needed to avoid many // calls to findViewById @@ -404,9 +419,13 @@ object KeyguardRootViewBinder { private class OnLayoutChange( private val viewModel: KeyguardRootViewModel, + private val blueprintViewModel: KeyguardBlueprintViewModel, + private val clockViewModel: KeyguardClockViewModel, private val childViews: Map<Int, View>, private val burnInParams: MutableStateFlow<BurnInParameters>, ) : OnLayoutChangeListener { + var prevTransition: TransitionData? = null + override fun onLayoutChange( view: View, left: Int, @@ -418,11 +437,21 @@ object KeyguardRootViewBinder { oldRight: Int, oldBottom: Int ) { + // After layout, ensure the notifications are positioned correctly childViews[nsslPlaceholderId]?.let { notificationListPlaceholder -> - // After layout, ensure the notifications are positioned correctly + // Do not update a second time while a blueprint transition is running + val transition = blueprintViewModel.currentTransition.value + val shouldAnimate = transition != null && transition.config.type.animateNotifChanges + if (prevTransition == transition && shouldAnimate) { + if (DEBUG) Log.w(TAG, "Skipping; layout during transition") + return + } + + prevTransition = transition viewModel.onNotificationContainerBoundsChanged( notificationListPlaceholder.top.toFloat(), notificationListPlaceholder.bottom.toFloat(), + animate = shouldAnimate ) } @@ -585,4 +614,6 @@ object KeyguardRootViewBinder { private const val ID = "occluding_app_device_entry_unlock_msg" private const val AOD_ICONS_APPEAR_DURATION: Long = 200 + private const val TAG = "KeyguardRootViewBinder" + private const val DEBUG = false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index fb1853f13ce9..777c873e47f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -68,7 +68,9 @@ import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel @@ -134,6 +136,7 @@ constructor( private val vibratorHelper: VibratorHelper, private val indicationController: KeyguardIndicationController, private val keyguardRootViewModel: KeyguardRootViewModel, + private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel, @Assisted bundle: Bundle, private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, private val chipbarCoordinator: ChipbarCoordinator, @@ -143,6 +146,7 @@ constructor( private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel, private val defaultShortcutsSection: DefaultShortcutsSection, private val keyguardClockInteractor: KeyguardClockInteractor, + private val keyguardClockViewModel: KeyguardClockViewModel, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) private val width: Int = bundle.getInt(KEY_VIEW_WIDTH) @@ -379,12 +383,14 @@ constructor( KeyguardRootViewBinder.bind( keyguardRootView, keyguardRootViewModel, + keyguardBlueprintViewModel, configuration, occludingAppDeviceEntryMessageViewModel, chipbarCoordinator, screenOffAnimationController, shadeInteractor, keyguardClockInteractor, + keyguardClockViewModel, null, // jank monitor not required for preview mode null, // device entry haptics not required preview mode null, // device entry haptics not required for preview mode 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/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt index 962cdf10cf86..c0266567a63f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout import androidx.core.text.isDigitsOnly +import com.android.systemui.CoreStartable import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.statusbar.commandline.Command @@ -31,10 +32,10 @@ constructor( private val commandRegistry: CommandRegistry, private val keyguardBlueprintRepository: KeyguardBlueprintRepository, private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor, -) { +) : CoreStartable { private val layoutCommand = KeyguardLayoutManagerCommand() - fun start() { + override fun start() { commandRegistry.registerCommand(COMMAND) { layoutCommand } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt index 04ac7bf1178e..2dc930121a71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt @@ -17,9 +17,14 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints +import com.android.systemui.CoreStartable +import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener import dagger.Binds import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet @Module @@ -41,4 +46,18 @@ abstract class KeyguardBlueprintModule { abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint( shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint ): KeyguardBlueprint + + @Binds + @IntoMap + @ClassKey(KeyguardBlueprintInteractor::class) + abstract fun bindsKeyguardBlueprintInteractor( + keyguardBlueprintInteractor: KeyguardBlueprintInteractor + ): CoreStartable + + @Binds + @IntoMap + @ClassKey(KeyguardBlueprintCommandListener::class) + abstract fun bindsKeyguardBlueprintCommandListener( + keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener + ): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt index c69d868866d0..39f1ebe25299 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints.transitions import android.transition.TransitionSet +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition import com.android.systemui.keyguard.ui.view.layout.sections.transitions.DefaultClockSteppingTransition import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel @@ -30,22 +31,23 @@ class IntraBlueprintTransition( enum class Type( val priority: Int, + val animateNotifChanges: Boolean, ) { - ClockSize(100), - ClockCenter(99), - DefaultClockStepping(98), - AodNotifIconsTransition(97), - SmartspaceVisibility(2), - DefaultTransition(1), + ClockSize(100, true), + ClockCenter(99, false), + DefaultClockStepping(98, false), + SmartspaceVisibility(2, true), + DefaultTransition(1, false), // When transition between blueprint, we don't need any duration or interpolator but we need // all elements go to correct state - NoTransition(0), + NoTransition(0, false), } data class Config( val type: Type, val checkPriority: Boolean = true, val terminatePrevious: Boolean = true, + val rebuildSections: List<KeyguardSection> = listOf(), ) { companion object { val DEFAULT = Config(Type.NoTransition) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 2832e9d8a35e..d77b54825664 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -19,6 +19,8 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM @@ -29,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.res.R import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder @@ -38,6 +41,7 @@ import com.android.systemui.statusbar.notification.shared.NotificationIconContai import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.ui.SystemBarUtilsState +import com.android.systemui.util.ui.value import javax.inject.Inject import kotlinx.coroutines.DisposableHandle @@ -51,6 +55,7 @@ constructor( private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, private val notificationIconAreaController: NotificationIconAreaController, private val systemBarUtilsState: SystemBarUtilsState, + private val rootViewModel: KeyguardRootViewModel, ) : KeyguardSection() { private var nicBindingDisposable: DisposableHandle? = null @@ -101,20 +106,14 @@ constructor( if (!MigrateClocksToBlueprint.isEnabled) { return } + val bottomMargin = context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin) - - val useSplitShade = context.resources.getBoolean(R.bool.config_use_split_notification_shade) - - val topAlignment = - if (useSplitShade) { - TOP - } else { - BOTTOM - } + val isVisible = rootViewModel.isNotifIconContainerVisible.value constraintSet.apply { connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin) setGoneMargin(nicId, BOTTOM, bottomMargin) + setVisibility(nicId, if (isVisible.value) VISIBLE else GONE) connect( nicId, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index b367715f529e..34a1da54c123 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -32,6 +32,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.systemui.customization.R as custR +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor @@ -57,6 +58,7 @@ internal fun ConstraintSet.setAlpha( alpha: Float, ) = views.forEach { view -> this.setAlpha(view.id, alpha) } +@SysUISingleton class ClockSection @Inject constructor( @@ -72,6 +74,7 @@ constructor( if (!MigrateClocksToBlueprint.isEnabled) { return } + KeyguardClockViewBinder.bind( this, constraintLayout, @@ -86,6 +89,7 @@ constructor( if (!MigrateClocksToBlueprint.isEnabled) { return } + keyguardClockViewModel.currentClock.value?.let { clock -> constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet)) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 0b8376af811c..c5fab8f57822 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -219,5 +219,37 @@ constructor( sensorRect.left ) } + + // This is only intended to be here until the KeyguardBottomAreaRefactor flag is enabled + // Without this logic, the lock icon location changes but the KeyguardBottomAreaView is not + // updated and visible ui layout jank occurs. This is due to AmbientIndicationContainer + // being in NPVC and laying out prior to the KeyguardRootView. + // Remove when both DeviceEntryUdfpsRefactor and KeyguardBottomAreaRefactor are enabled. + if (DeviceEntryUdfpsRefactor.isEnabled && !KeyguardBottomAreaRefactor.isEnabled) { + with(notificationPanelView) { + val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value + val bottomAreaViewRight = findViewById<View>(R.id.keyguard_bottom_area)?.right ?: 0 + findViewById<View>(R.id.ambient_indication_container)?.let { + val (ambientLeft, ambientTop) = it.locationOnScreen + if (isUdfpsSupported) { + // make top of ambient indication view the bottom of the lock icon + it.layout( + ambientLeft, + sensorRect.bottom, + bottomAreaViewRight - ambientLeft, + ambientTop + it.measuredHeight + ) + } else { + // make bottom of ambient indication view the top of the lock icon + it.layout( + ambientLeft, + sensorRect.top - it.measuredHeight, + bottomAreaViewRight - ambientLeft, + sensorRect.top + ) + } + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 487c2e918e5f..2d6690fbbc99 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -22,6 +22,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor @@ -36,6 +37,7 @@ import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import dagger.Lazy import javax.inject.Inject +@SysUISingleton open class SmartspaceSection @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 7c745bc227f0..f17dbd24cf25 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -369,6 +369,21 @@ class ClockSizeTransition( addTarget(R.id.status_view_media_container) } + override fun mutateBounds( + view: View, + fromIsVis: Boolean, + toIsVis: Boolean, + fromBounds: Rect, + toBounds: Rect, + fromSSBounds: Rect?, + toSSBounds: Rect? + ) { + // If view is changing visibility, hold it in place + if (fromIsVis == toIsVis) return + if (DEBUG) Log.i(TAG, "Holding position of ${view.id}") + toBounds.set(fromBounds) + } + companion object { const val STATUS_AREA_MOVE_UP_MILLIS = 967L const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L 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/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt index b1f189836903..7ac03bffd4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt @@ -17,15 +17,119 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.os.Handler +import android.transition.Transition +import android.transition.TransitionManager +import android.util.Log +import androidx.constraintlayout.widget.ConstraintLayout +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor +import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +data class TransitionData( + val config: Config, + val start: Long = System.currentTimeMillis(), +) class KeyguardBlueprintViewModel @Inject constructor( + @Main private val handler: Handler, keyguardBlueprintInteractor: KeyguardBlueprintInteractor, ) { val blueprint = keyguardBlueprintInteractor.blueprint val blueprintId = keyguardBlueprintInteractor.blueprintId val refreshTransition = keyguardBlueprintInteractor.refreshTransition + + private val _currentTransition = MutableStateFlow<TransitionData?>(null) + val currentTransition = _currentTransition.asStateFlow() + + private val runningTransitions = mutableSetOf<Transition>() + private val transitionListener = + object : Transition.TransitionListener { + override fun onTransitionCancel(transition: Transition) { + if (DEBUG) Log.e(TAG, "onTransitionCancel: ${transition::class.simpleName}") + updateTransitions(null) { remove(transition) } + } + + override fun onTransitionEnd(transition: Transition) { + if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}") + updateTransitions(null) { remove(transition) } + } + + override fun onTransitionPause(transition: Transition) { + if (DEBUG) Log.i(TAG, "onTransitionPause: ${transition::class.simpleName}") + updateTransitions(null) { remove(transition) } + } + + override fun onTransitionResume(transition: Transition) { + if (DEBUG) Log.i(TAG, "onTransitionResume: ${transition::class.simpleName}") + updateTransitions(null) { add(transition) } + } + + override fun onTransitionStart(transition: Transition) { + if (DEBUG) Log.i(TAG, "onTransitionStart: ${transition::class.simpleName}") + updateTransitions(null) { add(transition) } + } + } + + fun updateTransitions(data: TransitionData?, mutate: MutableSet<Transition>.() -> Unit) { + runningTransitions.mutate() + + if (runningTransitions.size <= 0) _currentTransition.value = null + else if (data != null) _currentTransition.value = data + } + + fun runTransition( + constraintLayout: ConstraintLayout, + transition: Transition, + config: Config, + apply: () -> Unit, + ) { + val currentPriority = currentTransition.value?.let { it.config.type.priority } ?: -1 + if (config.checkPriority && config.type.priority < currentPriority) { + if (DEBUG) { + Log.w( + TAG, + "runTransition: skipping ${transition::class.simpleName}: " + + "currentPriority=$currentPriority; config=$config" + ) + } + apply() + return + } + + if (DEBUG) { + Log.i( + TAG, + "runTransition: running ${transition::class.simpleName}: " + + "currentPriority=$currentPriority; config=$config" + ) + } + + // beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to + // the running set until the copy is started by the handler. + updateTransitions(TransitionData(config)) { add(transition) } + transition.addListener(transitionListener) + + handler.post { + if (config.terminatePrevious) { + TransitionManager.endTransitions(constraintLayout) + } + + TransitionManager.beginDelayedTransition(constraintLayout, transition) + apply() + + // Delay removal until after copied transition has started + handler.post { updateTransitions(null) { remove(transition) } } + } + } + + companion object { + private const val TAG = "KeyguardBlueprintViewModel" + private const val DEBUG = true + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt index 8409f15dca81..448a71c36a99 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt @@ -23,9 +23,12 @@ import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -42,6 +45,7 @@ constructor( private val burnInInteractor: BurnInInteractor, private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, configurationInteractor: ConfigurationInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /** Notifies when a new configuration is set */ @@ -69,12 +73,22 @@ constructor( .distinctUntilChanged() } + @OptIn(ExperimentalCoroutinesApi::class) private val burnIn: Flow<BurnInModel> = - burnInInteractor - .burnIn( - xDimenResourceId = R.dimen.burn_in_prevention_offset_x, - yDimenResourceId = R.dimen.default_burn_in_prevention_offset, - ) + combine( + burnInInteractor.burnIn( + xDimenResourceId = R.dimen.burn_in_prevention_offset_x, + yDimenResourceId = R.dimen.default_burn_in_prevention_offset, + ), + keyguardTransitionInteractor.transitionValue(KeyguardState.AOD), + ) { burnIn, aodTransitionValue -> + BurnInModel( + (burnIn.translationX * aodTransitionValue).toInt(), + (burnIn.translationY * aodTransitionValue).toInt(), + burnIn.scale, + burnIn.scaleClockOnly, + ) + } .distinctUntilChanged() /** An observable for the x-offset by which the indication area should be translated. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt index c4383fc0857d..244d842b7073 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -28,19 +29,23 @@ import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAfforda import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) class KeyguardQuickAffordancesCombinedViewModel @Inject constructor( + @Application applicationScope: CoroutineScope, private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor, private val keyguardInteractor: KeyguardInteractor, shadeInteractor: ShadeInteractor, @@ -84,15 +89,20 @@ constructor( /** The only time the expansion is important is while lockscreen is actively displayed */ private val shadeExpansionAlpha = combine( - showingLockscreen, - shadeInteractor.anyExpansion, - ) { showingLockscreen, expansion -> - if (showingLockscreen) { - 1 - expansion - } else { - 0f + showingLockscreen, + shadeInteractor.anyExpansion, + ) { showingLockscreen, expansion -> + if (showingLockscreen) { + 1 - expansion + } else { + 0f + } } - } + .stateIn( + scope = applicationScope, + started = SharingStarted.Lazily, + initialValue = 0f, + ) /** * ID of the slot that's currently selected in the preview that renders exclusively in the diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index aaec69f4f022..1ec2a4969f0d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -58,6 +58,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform @@ -67,13 +69,14 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardRootViewModel @Inject constructor( - @Application private val scope: CoroutineScope, + @Application private val applicationScope: CoroutineScope, private val deviceEntryInteractor: DeviceEntryInteractor, private val dozeParameters: DozeParameters, private val keyguardInteractor: KeyguardInteractor, @@ -280,7 +283,7 @@ constructor( burnInJob?.cancel() burnInJob = - scope.launch("$TAG#aodBurnInViewModel") { + applicationScope.launch("$TAG#aodBurnInViewModel") { aodBurnInViewModel.movement(params).collect { _burnInModel.value = it } } } @@ -294,7 +297,7 @@ constructor( } /** Is the notification icon container visible? */ - val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> = + val isNotifIconContainerVisible: StateFlow<AnimatedValue<Boolean>> = combine( goneToAodTransitionRunning, keyguardTransitionInteractor.finishedKeyguardState.map { @@ -336,11 +339,15 @@ constructor( } } } - .distinctUntilChanged() + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = AnimatedValue.NotAnimating(false), + ) - fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) { + fun onNotificationContainerBoundsChanged(top: Float, bottom: Float, animate: Boolean = false) { keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = top, bottom = bottom) + NotificationContainerBounds(top = top, bottom = bottom, isAnimated = animate) ) } 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/media/controls/domain/MediaDomainModule.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt index e0c54190283a..9c29bab80d14 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt @@ -43,6 +43,7 @@ interface MediaDomainModule { @IntoMap @ClassKey(MediaDataProcessor::class) fun bindMediaDataProcessor(interactor: MediaDataProcessor): CoreStartable + companion object { @Provides @@ -52,7 +53,7 @@ interface MediaDomainModule { newProvider: Provider<MediaCarouselInteractor>, mediaFlags: MediaFlags, ): MediaDataManager { - return if (mediaFlags.isMediaControlsRefactorEnabled()) { + return if (mediaFlags.isSceneContainerEnabled()) { newProvider.get() } else { legacyProvider.get() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index eed775242d1f..8e985e11732f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -269,7 +269,7 @@ class MediaDataProcessor( } override fun start() { - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { return } @@ -746,8 +746,7 @@ class MediaDataProcessor( notif.extras.getParcelable( Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo::class.java - ) - ?: getAppInfoFromPackage(sbn.packageName) + ) ?: getAppInfoFromPackage(sbn.packageName) // App name val appName = getAppName(sbn, appInfo) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt index 486d4d46c767..aa93df7f1474 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt @@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController +import android.media.session.MediaController.PlaybackInfo import android.text.TextUtils import android.util.Log import androidx.annotation.AnyThread @@ -74,6 +75,11 @@ constructor( private val listeners: MutableSet<Listener> = mutableSetOf() private val entries: MutableMap<String, Entry> = mutableMapOf() + companion object { + private val EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA = + MediaDeviceData(enabled = false, icon = null, name = null, showBroadcastButton = false) + } + /** Add a listener for changes to the media route (ie. device). */ fun addListener(listener: Listener) = listeners.add(listener) @@ -333,28 +339,32 @@ constructor( @WorkerThread private fun updateCurrent() { if (isLeAudioBroadcastEnabled()) { - if (enableLeAudioSharing()) { - current = - MediaDeviceData( - enabled = false, - icon = - context.getDrawable( - com.android.settingslib.R.drawable.ic_bt_le_audio_sharing - ), - name = context.getString(R.string.audio_sharing_description), - intent = null, - showBroadcastButton = false - ) + current = getLeAudioBroadcastDeviceData() + } else if (Flags.usePlaybackInfoForRoutingControls()) { + val activeDevice: MediaDeviceData? + + // LocalMediaManager provides the connected device based on PlaybackInfo. + // TODO (b/342197065): Simplify nullability once we make currentConnectedDevice + // non-null. + val connectedDevice = localMediaManager.currentConnectedDevice?.toMediaDeviceData() + + if (controller?.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) { + val routingSession = + mr2manager.get().getRoutingSessionForMediaController(controller) + + activeDevice = + routingSession?.let { + // For a remote session, always use the current device from + // LocalMediaManager. Override with routing session name if available to + // show dynamic group name. + connectedDevice?.copy(name = it.name ?: connectedDevice.name) + } } else { - current = - MediaDeviceData( - /* enabled */ true, - /* icon */ context.getDrawable(R.drawable.settings_input_antenna), - /* name */ broadcastDescription, - /* intent */ null, - /* showBroadcastButton */ showBroadcastButton = true - ) + // Prefer SASS if available when playback is local. + activeDevice = getSassDevice() ?: connectedDevice } + + current = activeDevice ?: EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA } else { val aboutToConnect = aboutToConnectDeviceOverride if ( @@ -389,6 +399,43 @@ constructor( } } + private fun getSassDevice(): MediaDeviceData? { + val sassDevice = aboutToConnectDeviceOverride ?: return null + return sassDevice.fullMediaDevice?.toMediaDeviceData() + ?: sassDevice.backupMediaDeviceData + } + + private fun MediaDevice.toMediaDeviceData() = + MediaDeviceData( + enabled = true, + icon = iconWithoutBackground, + name = name, + id = id, + showBroadcastButton = false + ) + + private fun getLeAudioBroadcastDeviceData(): MediaDeviceData { + return if (enableLeAudioSharing()) { + MediaDeviceData( + enabled = false, + icon = + context.getDrawable( + com.android.settingslib.R.drawable.ic_bt_le_audio_sharing + ), + name = context.getString(R.string.audio_sharing_description), + intent = null, + showBroadcastButton = false + ) + } else { + MediaDeviceData( + enabled = true, + icon = context.getDrawable(R.drawable.settings_input_antenna), + name = broadcastDescription, + intent = null, + showBroadcastButton = true + ) + } + } /** Return a display name for the current device / route, or null if not possible */ private fun getDeviceName( device: MediaDevice?, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt index 9e6230012760..b4bd4fd2c266 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt @@ -36,8 +36,8 @@ import com.android.systemui.media.controls.domain.pipeline.MediaSessionBasedFilt import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener import com.android.systemui.media.controls.domain.resume.MediaResumeListener import com.android.systemui.media.controls.shared.model.MediaCommonModel -import com.android.systemui.media.controls.util.MediaControlsRefactorFlag import com.android.systemui.media.controls.util.MediaFlags +import com.android.systemui.scene.shared.flag.SceneContainerFlag import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -127,7 +127,7 @@ constructor( val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia override fun start() { - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { return } @@ -256,8 +256,6 @@ constructor( companion object { val unsupported: Nothing get() = - error( - "Code path not supported when ${MediaControlsRefactorFlag.FLAG_NAME} is enabled" - ) + error("Code path not supported when ${SceneContainerFlag.DESCRIPTION} is enabled") } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt index fed93f037638..72fb218865b2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -157,7 +157,6 @@ object MediaControlViewBinder { viewController, backgroundDispatcher, mainDispatcher, - mediaFlags, isSongUpdated ) @@ -414,7 +413,6 @@ object MediaControlViewBinder { viewController: MediaViewController, backgroundDispatcher: CoroutineDispatcher, mainDispatcher: CoroutineDispatcher, - mediaFlags: MediaFlags, updateBackground: Boolean, ) { val traceCookie = viewHolder.hashCode() @@ -424,13 +422,8 @@ object MediaControlViewBinder { viewController.isArtworkBound = false } // Capture width & height from views in foreground for artwork scaling in background - var width = viewHolder.albumView.measuredWidth - var height = viewHolder.albumView.measuredHeight - if (mediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) { - // TODO(b/312714128): ensure we have a valid size before setting background - width = viewController.widthInSceneContainerPx - height = viewController.heightInSceneContainerPx - } + val width = viewController.widthInSceneContainerPx + val height = viewController.heightInSceneContainerPx withContext(backgroundDispatcher) { val artwork = if (viewModel.shouldAddGradient) { @@ -449,6 +442,11 @@ object MediaControlViewBinder { val colorSchemeChanged = viewController.colorSchemeTransition.updateColorScheme(viewModel.colorScheme) val albumView = viewHolder.albumView + + // Set up width of album view constraint. + viewController.expandedLayout.getConstraint(albumView.id).layout.mWidth = width + viewController.collapsedLayout.getConstraint(albumView.id).layout.mWidth = width + albumView.setPadding(0, 0, 0, 0) if ( updateBackground || diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 19e3e0715989..8316b3aba73e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -217,7 +217,7 @@ constructor( private val animationScaleObserver: ContentObserver = object : ContentObserver(null) { override fun onChange(selfChange: Boolean) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() } } else { controllerByViewModel.values.forEach { it.updateAnimatorDurationScale() } @@ -347,7 +347,7 @@ constructor( inflateSettingsButton() mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) configurationController.addCallback(configListener) - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { setUpListeners() } else { val visualStabilityCallback = OnReorderingAllowedListener { @@ -389,7 +389,7 @@ constructor( listenForAnyStateToLockscreenTransition(this) listenForLockscreenSettingChanges(this) - if (!mediaFlags.isMediaControlsRefactorEnabled()) return@repeatOnLifecycle + if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle listenForMediaItemsChanges(this) } } @@ -882,8 +882,7 @@ constructor( val previousVisibleIndex = MediaPlayerData.playerKeys().indexOfFirst { key -> it == key } mediaCarouselScrollHandler.scrollToPlayer(previousVisibleIndex, mediaIndex) - } - ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex) + } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex) } } else if (isRtl && mediaContent.childCount > 0) { // In RTL, Scroll to the first player as it is the rightmost player in media carousel. @@ -1092,7 +1091,7 @@ constructor( } private fun updatePlayers(recreateMedia: Boolean) { - if (mediaFlags.isMediaControlsRefactorEnabled()) { + if (mediaFlags.isSceneContainerEnabled()) { updateMediaPlayers(recreateMedia) return } @@ -1192,7 +1191,7 @@ constructor( currentStartLocation = startLocation currentEndLocation = endLocation currentTransitionProgress = progress - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { for (mediaPlayer in MediaPlayerData.players()) { updateViewControllerToState(mediaPlayer.mediaViewController, immediately) } @@ -1254,7 +1253,7 @@ constructor( /** Update listening to seekbar. */ private fun updateSeekbarListening(visibleToUser: Boolean) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { for (player in MediaPlayerData.players()) { player.setListening(visibleToUser && currentlyExpanded) } @@ -1269,7 +1268,7 @@ constructor( private fun updateCarouselDimensions() { var width = 0 var height = 0 - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { for (mediaPlayer in MediaPlayerData.players()) { val controller = mediaPlayer.mediaViewController // When transitioning the view to gone, the view gets smaller, but the translation @@ -1361,7 +1360,7 @@ constructor( !mediaManager.hasActiveMediaOrRecommendation() && desiredHostState.showsOnlyActiveMedia - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { for (mediaPlayer in MediaPlayerData.players()) { if (animate) { mediaPlayer.mediaViewController.animatePendingStateChange( @@ -1401,7 +1400,7 @@ constructor( } fun closeGuts(immediate: Boolean = true) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { MediaPlayerData.players().forEach { it.closeGuts(immediate) } } else { controllerByViewModel.values.forEach { it.closeGuts(immediate) } @@ -1544,7 +1543,7 @@ constructor( @VisibleForTesting fun onSwipeToDismiss() { - if (mediaFlags.isMediaControlsRefactorEnabled()) { + if (mediaFlags.isSceneContainerEnabled()) { mediaCarouselViewModel.onSwipeToDismiss() return } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index a4f3e2174791..6589038d7096 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -42,6 +42,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.util.MediaFlags @@ -61,6 +62,11 @@ import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch private val TAG: String = MediaHierarchyManager::class.java.simpleName @@ -89,6 +95,7 @@ val View.isShownNotFaded: Boolean * This manager is responsible for placement of the unique media view between the different hosts * and animate the positions of the views to achieve seamless transitions. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MediaHierarchyManager @Inject @@ -101,6 +108,7 @@ constructor( private val mediaManager: MediaDataManager, private val keyguardViewController: KeyguardViewController, private val dreamOverlayStateController: DreamOverlayStateController, + private val keyguardInteractor: KeyguardInteractor, communalTransitionViewModel: CommunalTransitionViewModel, configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, @@ -236,6 +244,15 @@ constructor( private var inSplitShade = false + /** + * Whether we are transitioning to the hub or from the hub to the shade. If so, use fade as the + * transformation type and skip calculating state with the bounds and the transition progress. + */ + private val isHubTransition + get() = + desiredLocation == LOCATION_COMMUNAL_HUB || + (previousLocation == LOCATION_COMMUNAL_HUB && desiredLocation == LOCATION_QS) + /** Is there any active media or recommendation in the carousel? */ private var hasActiveMediaOrRecommendation: Boolean = false get() = mediaManager.hasActiveMediaOrRecommendation() @@ -413,6 +430,12 @@ constructor( /** Is the communal UI showing */ private var isCommunalShowing: Boolean = false + /** Is the communal UI showing and not dreaming */ + private var onCommunalNotDreaming: Boolean = false + + /** Is the communal UI showing, dreaming and shade expanding */ + private var onCommunalDreamingAndShadeExpanding: Boolean = false + /** * The current cross fade progress. 0.5f means it's just switching between the start and the end * location and the content is fully faded, while 0.75f means that we're halfway faded in again @@ -585,11 +608,26 @@ constructor( // Listen to the communal UI state. Make sure that communal UI is showing and hub itself is // available, ie. not disabled and able to be shown. + // When dreaming, qs expansion is immediately set to 1f, so we listen to shade expansion to + // calculate the new location. coroutineScope.launch { - communalTransitionViewModel.isUmoOnCommunal.collect { value -> - isCommunalShowing = value - updateDesiredLocation(forceNoAnimation = true) - } + combine( + communalTransitionViewModel.isUmoOnCommunal, + keyguardInteractor.isDreaming, + // keep on communal before the shade is expanded enough to show the elements in + // QS + shadeInteractor.shadeExpansion + .mapLatest { it < EXPANSION_THRESHOLD } + .distinctUntilChanged(), + ::Triple + ) + .collectLatest { (communalShowing, isDreaming, isShadeExpanding) -> + isCommunalShowing = communalShowing + onCommunalDreamingAndShadeExpanding = + communalShowing && isDreaming && isShadeExpanding + onCommunalNotDreaming = communalShowing && !isDreaming + updateDesiredLocation(forceNoAnimation = true) + } } } @@ -805,6 +843,9 @@ constructor( if (skipQqsOnExpansion) { return false } + if (isHubTransition) { + return false + } // This is an invalid transition, and can happen when using the camera gesture from the // lock screen. Disallow. if ( @@ -947,6 +988,9 @@ constructor( @VisibleForTesting @TransformationType fun calculateTransformationType(): Int { + if (isHubTransition) { + return TRANSFORMATION_TYPE_FADE + } if (isTransitioningToFullShade) { if (inSplitShade && areGuidedTransitionHostsVisible()) { return TRANSFORMATION_TYPE_TRANSITION @@ -977,7 +1021,7 @@ constructor( * otherwise */ private fun getTransformationProgress(): Float { - if (skipQqsOnExpansion) { + if (skipQqsOnExpansion || isHubTransition) { return -1.0f } val progress = getQSTransformationProgress() @@ -1147,15 +1191,18 @@ constructor( } val onLockscreen = (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD)) + + // UMO should show on hub unless the qs is expanding when not dreaming, or shade is + // expanding when dreaming + val onCommunal = + (onCommunalNotDreaming && qsExpansion == 0.0f) || onCommunalDreamingAndShadeExpanding val location = when { mediaFlags.isSceneContainerEnabled() -> desiredLocation dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY - - // UMO should show in communal unless the shade is expanding or visible. - isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB + onCommunal -> LOCATION_COMMUNAL_HUB (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS - qsExpansion > 0.4f && onLockscreen -> LOCATION_QS + qsExpansion > EXPANSION_THRESHOLD && onLockscreen -> LOCATION_QS onLockscreen && isSplitShadeExpanding() -> LOCATION_QS onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS @@ -1190,6 +1237,9 @@ constructor( // reattach it without an animation return LOCATION_LOCKSCREEN } + // When communal showing while dreaming, skipQqsOnExpansion is also true but we want to + // return the calculated location, so it won't disappear as soon as shade is pulled down. + if (isCommunalShowing) return location if (skipQqsOnExpansion) { // When doing an immediate expand or collapse, we want to keep it in QS. return LOCATION_QS @@ -1288,6 +1338,9 @@ constructor( * transitioning */ const val TRANSFORMATION_TYPE_FADE = 1 + + /** Expansion amount value at which elements start to become visible in the QS panel. */ + const val EXPANSION_THRESHOLD = 0.4f } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 38377088a2d7..9d0723211d4b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -203,7 +203,7 @@ constructor( private val scrubbingChangeListener = object : SeekBarViewModel.ScrubbingChangeListener { override fun onScrubbingChanged(scrubbing: Boolean) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return if (isScrubbing == scrubbing) return isScrubbing = scrubbing updateDisplayForScrubbingChange() @@ -213,7 +213,7 @@ constructor( private val enabledChangeListener = object : SeekBarViewModel.EnabledChangeListener { override fun onEnabledChanged(enabled: Boolean) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return if (isSeekBarEnabled == enabled) return isSeekBarEnabled = enabled MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled) @@ -229,7 +229,7 @@ constructor( * @param listening True when player should be active. Otherwise, false. */ fun setListening(listening: Boolean) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return seekBarViewModel.listening = listening } @@ -263,7 +263,7 @@ constructor( ) ) } - if (mediaFlags.isMediaControlsRefactorEnabled()) { + if (mediaFlags.isSceneContainerEnabled()) { if ( this@MediaViewController::recsConfigurationChangeListener.isInitialized ) { @@ -305,6 +305,7 @@ constructor( */ var collapsedLayout = ConstraintSet() @VisibleForTesting set + /** * The expanded constraint set used to render a collapsed player. If it is modified, make sure * to call [refreshState] @@ -334,7 +335,7 @@ constructor( * Notify this controller that the view has been removed and all listeners should be destroyed */ fun onDestroy() { - if (mediaFlags.isMediaControlsRefactorEnabled()) { + if (mediaFlags.isSceneContainerEnabled()) { if (this::seekBarObserver.isInitialized) { seekBarViewModel.progress.removeObserver(seekBarObserver) } @@ -657,7 +658,7 @@ constructor( } fun attachPlayer(mediaViewHolder: MediaViewHolder) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return this.mediaViewHolder = mediaViewHolder // Setting up seek bar. @@ -731,7 +732,7 @@ constructor( } fun updateAnimatorDurationScale() { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return if (this::seekBarObserver.isInitialized) { seekBarObserver.animationEnabled = globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f @@ -787,7 +788,7 @@ constructor( } fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return this.recommendationViewHolder = recommendationViewHolder attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION) @@ -796,13 +797,13 @@ constructor( } fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return seekBarViewModel.logSeek = onSeek onBindSeekBar.invoke(seekBarViewModel) } fun setUpTurbulenceNoise() { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return if (!this::turbulenceNoiseAnimationConfig.isInitialized) { turbulenceNoiseAnimationConfig = createTurbulenceNoiseConfig( @@ -1153,13 +1154,13 @@ constructor( } fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return isPrevButtonAvailable = isAvailable prevNotVisibleValue = notVisibleValue } fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return isNextButtonAvailable = isAvailable nextNotVisibleValue = notVisibleValue } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt deleted file mode 100644 index 2850b4bb2358..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt +++ /dev/null @@ -1,53 +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.systemui.media.controls.util - -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -/** Helper for reading or using the media_controls_refactor flag state. */ -@Suppress("NOTHING_TO_INLINE") -object MediaControlsRefactorFlag { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_MEDIA_CONTROLS_REFACTOR - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the flag enabled? */ - @JvmStatic - inline val isEnabled - get() = Flags.mediaControlsRefactor() - - /** - * Called to ensure code is only run when the flag is enabled. This protects users from the - * unintended behaviors caused by accidentally running new logic, while also crashing on an eng - * build to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) - - /** - * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is enabled to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 1e7bc0cacf1d..21c311191710 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -52,8 +52,4 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClass /** Check whether to use scene framework */ fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled - - /** Check whether to use media refactor code */ - fun isMediaControlsRefactorEnabled() = - MediaControlsRefactorFlag.isEnabled && SceneContainerFlag.isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt index a618490c1b53..de56c8493d98 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt @@ -21,15 +21,17 @@ import android.os.Parcel import android.os.Parcelable /** - * Class that represents an area that should be captured. Currently it has only a launch cookie that - * represents a task but we potentially could add more identifiers e.g. for a pair of tasks. + * Class that represents an area that should be captured. Currently it has only a launch cookie and + * id that represents a task but we potentially could add more identifiers e.g. for a pair of tasks. */ -data class MediaProjectionCaptureTarget(val launchCookie: LaunchCookie?) : Parcelable { +data class MediaProjectionCaptureTarget(val launchCookie: LaunchCookie?, val taskId: Int) : + Parcelable { - constructor(parcel: Parcel) : this(LaunchCookie.readFromParcel(parcel)) + constructor(parcel: Parcel) : this(LaunchCookie.readFromParcel(parcel), parcel.readInt()) override fun writeToParcel(dest: Parcel, flags: Int) { LaunchCookie.writeToParcel(launchCookie, dest) + dest.writeInt(taskId) } override fun describeContents(): Int = 0 diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt new file mode 100644 index 000000000000..34894599aaf9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionModule.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection + +import com.android.systemui.mediaprojection.data.repository.MediaProjectionManagerRepository +import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository +import dagger.Binds +import dagger.Module + +@Module +interface MediaProjectionModule { + @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index 4685c5a0cb21..d6affd2f0250 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -174,7 +174,7 @@ class MediaProjectionAppSelectorActivity( // is created and ready to be captured. val activityStarted = activityLauncher.startActivityAsUser(intent, userHandle, activityOptions.toBundle()) { - returnSelectedApp(launchCookie) + returnSelectedApp(launchCookie, taskId = -1) } // Rely on the ActivityManager to pop up a dialog regarding app suspension @@ -232,7 +232,7 @@ class MediaProjectionAppSelectorActivity( } } - override fun returnSelectedApp(launchCookie: LaunchCookie) { + override fun returnSelectedApp(launchCookie: LaunchCookie, taskId: Int) { taskSelected = true if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) { // The client requested to return the result in the result receiver instead of @@ -242,7 +242,7 @@ class MediaProjectionAppSelectorActivity( EXTRA_CAPTURE_REGION_RESULT_RECEIVER, ResultReceiver::class.java ) as ResultReceiver - val captureRegion = MediaProjectionCaptureTarget(launchCookie) + val captureRegion = MediaProjectionCaptureTarget(launchCookie, taskId) val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) } resultReceiver.send(RESULT_OK, data) // TODO(b/279175710): Ensure consent result is always set here. Skipping this for now @@ -255,6 +255,7 @@ class MediaProjectionAppSelectorActivity( val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder) projection.setLaunchCookie(launchCookie) + projection.setTaskId(taskId) val intent = Intent() intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder()) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index f08bc17c4f23..9b1ca1ec0558 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -65,7 +65,7 @@ import kotlinx.coroutines.SupervisorJob subcomponents = [MediaProjectionAppSelectorComponent::class], includes = [MediaProjectionDevicePolicyModule::class] ) -interface MediaProjectionModule { +interface MediaProjectionActivitiesModule { @Binds @IntoMap @ClassKey(MediaProjectionAppSelectorActivity::class) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt index f204b3e74f4b..6857000169e5 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt @@ -9,7 +9,10 @@ import android.app.ActivityOptions.LaunchCookie interface MediaProjectionAppSelectorResultHandler { /** * Return selected app to the original caller of the media projection app picker. - * @param launchCookie launch cookie of the launched activity of the target app + * @param launchCookie launch cookie of the launched activity of the target app, always set + * regardless of launching a new task or a recent task + * @param taskId id of the launched task of the target app, only set to a positive int when + * launching a recent task, otherwise set to -1 by default */ - fun returnSelectedApp(launchCookie: LaunchCookie) + fun returnSelectedApp(launchCookie: LaunchCookie, taskId: Int) } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index 9549ab1cab3e..46aa0644035c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -144,10 +144,9 @@ constructor( activityOptions.launchDisplayId = task.displayId activityOptions.setLaunchCookie(launchCookie) - val handleResult: () -> Unit = { resultHandler.returnSelectedApp(launchCookie)} - val taskId = task.taskId val splitBounds = task.splitBounds + val handleResult: () -> Unit = { resultHandler.returnSelectedApp(launchCookie, taskId)} if (pssAppSelectorRecentsSplitScreen() && task.isLaunchingInSplitScreen() && diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt index cfbcaf91b791..1d5f6f52000c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.mediaprojection.taskswitcher.data.model +package com.android.systemui.mediaprojection.data.model import android.app.ActivityManager.RunningTaskInfo diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt index 74d19921c706..3ce0a1e00910 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.mediaprojection.taskswitcher.data.repository +package com.android.systemui.mediaprojection.data.repository import android.app.ActivityManager.RunningTaskInfo import android.media.projection.MediaProjectionInfo @@ -24,20 +24,21 @@ import android.util.Log import android.view.ContentRecordingSession import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.mediaprojection.MediaProjectionServiceHelper -import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -88,7 +89,11 @@ constructor( mediaProjectionManager.addCallback(callback, handler) awaitClose { mediaProjectionManager.removeCallback(callback) } } - .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1) + .stateIn( + scope = applicationScope, + started = SharingStarted.Lazily, + initialValue = MediaProjectionState.NotProjecting, + ) private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState { if (session == null) { diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt index e495466008ce..21300dbff929 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.systemui.mediaprojection.taskswitcher.data.repository +package com.android.systemui.mediaprojection.data.repository import android.app.ActivityManager.RunningTaskInfo -import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.data.model.MediaProjectionState import kotlinx.coroutines.flow.Flow /** Represents a repository to retrieve and change data related to media projection. */ diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt index 22ad07ebc3b1..eb38958e93f8 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt @@ -17,16 +17,11 @@ package com.android.systemui.mediaprojection.taskswitcher import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository -import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository -import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository import dagger.Binds import dagger.Module @Module interface MediaProjectionTaskSwitcherModule { - - @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository - @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt index eb9e6a5de057..c232d4d16294 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt @@ -21,8 +21,8 @@ import android.app.TaskInfo import android.content.Intent import android.util.Log import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState -import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository +import com.android.systemui.mediaprojection.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt index a256b59ac076..e931f8f5398a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt @@ -18,10 +18,7 @@ import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOn private const val TAG = "BackPanel" private const val DEBUG = false -class BackPanel( - context: Context, - private val latencyTracker: LatencyTracker -) : View(context) { +class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) { var arrowsPointLeft = false set(value) { @@ -42,39 +39,39 @@ class BackPanel( // True if the panel is currently on the left of the screen var isLeftPanel = false - /** - * Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw] - */ + /** Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw] */ private var trackingBackArrowLatency = false - /** - * The length of the arrow measured horizontally. Used for animating [arrowPath] - */ - private var arrowLength = AnimatedFloat( + /** The length of the arrow measured horizontally. Used for animating [arrowPath] */ + private var arrowLength = + AnimatedFloat( name = "arrowLength", minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS - ) + ) /** * The height of the arrow measured vertically from its center to its top (i.e. half the total * height). Used for animating [arrowPath] */ - var arrowHeight = AnimatedFloat( + var arrowHeight = + AnimatedFloat( name = "arrowHeight", minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ROTATION_DEGREES - ) + ) - val backgroundWidth = AnimatedFloat( + val backgroundWidth = + AnimatedFloat( name = "backgroundWidth", minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS, minimumValue = 0f, - ) + ) - val backgroundHeight = AnimatedFloat( + val backgroundHeight = + AnimatedFloat( name = "backgroundHeight", minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS, minimumValue = 0f, - ) + ) /** * Corners of the background closer to the edge of the screen (where the arrow appeared from). @@ -88,17 +85,19 @@ class BackPanel( */ val backgroundFarCornerRadius = AnimatedFloat("backgroundFarCornerRadius") - var scale = AnimatedFloat( + var scale = + AnimatedFloat( name = "scale", minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_SCALE, minimumValue = 0f - ) + ) - val scalePivotX = AnimatedFloat( + val scalePivotX = + AnimatedFloat( name = "scalePivotX", minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS, minimumValue = backgroundWidth.pos / 2, - ) + ) /** * Left/right position of the background relative to the canvas. Also corresponds with the @@ -107,21 +106,24 @@ class BackPanel( */ var horizontalTranslation = AnimatedFloat(name = "horizontalTranslation") - var arrowAlpha = AnimatedFloat( + var arrowAlpha = + AnimatedFloat( name = "arrowAlpha", minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA, minimumValue = 0f, maximumValue = 1f - ) + ) - val backgroundAlpha = AnimatedFloat( + val backgroundAlpha = + AnimatedFloat( name = "backgroundAlpha", minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA, minimumValue = 0f, maximumValue = 1f - ) + ) - private val allAnimatedFloat = setOf( + private val allAnimatedFloat = + setOf( arrowLength, arrowHeight, backgroundWidth, @@ -132,7 +134,7 @@ class BackPanel( horizontalTranslation, arrowAlpha, backgroundAlpha - ) + ) /** * Canvas vertical translation. How far up/down the arrow and background appear relative to the @@ -140,43 +142,45 @@ class BackPanel( */ var verticalTranslation = AnimatedFloat("verticalTranslation") - /** - * Use for drawing debug info. Can only be set if [DEBUG]=true - */ + /** Use for drawing debug info. Can only be set if [DEBUG]=true */ var drawDebugInfo: ((canvas: Canvas) -> Unit)? = null set(value) { if (DEBUG) field = value } internal fun updateArrowPaint(arrowThickness: Float) { - arrowPaint.strokeWidth = arrowThickness - val isDeviceInNightTheme = resources.configuration.uiMode and - Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + val isDeviceInNightTheme = + resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == + Configuration.UI_MODE_NIGHT_YES - arrowPaint.color = Utils.getColorAttrDefaultColor(context, + arrowPaint.color = + Utils.getColorAttrDefaultColor( + context, if (isDeviceInNightTheme) { com.android.internal.R.attr.materialColorOnSecondaryContainer } else { com.android.internal.R.attr.materialColorOnSecondaryFixed } - ) + ) - arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context, + arrowBackgroundPaint.color = + Utils.getColorAttrDefaultColor( + context, if (isDeviceInNightTheme) { com.android.internal.R.attr.materialColorSecondaryContainer } else { com.android.internal.R.attr.materialColorSecondaryFixedDim } - ) + ) } inner class AnimatedFloat( - name: String, - private val minimumVisibleChange: Float? = null, - private val minimumValue: Float? = null, - private val maximumValue: Float? = null, + name: String, + private val minimumVisibleChange: Float? = null, + private val minimumValue: Float? = null, + private val maximumValue: Float? = null, ) { // The resting position when not stretched by a touch drag @@ -207,19 +211,21 @@ class BackPanel( } init { - val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) { - override fun setValue(animatedFloat: AnimatedFloat, value: Float) { - animatedFloat.pos = value - } + val floatProp = + object : FloatPropertyCompat<AnimatedFloat>(name) { + override fun setValue(animatedFloat: AnimatedFloat, value: Float) { + animatedFloat.pos = value + } - override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos - } - animation = SpringAnimation(this, floatProp).apply { - spring = SpringForce() - this@AnimatedFloat.minimumValue?.let { setMinValue(it) } - this@AnimatedFloat.maximumValue?.let { setMaxValue(it) } - this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it } - } + override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos + } + animation = + SpringAnimation(this, floatProp).apply { + spring = SpringForce() + this@AnimatedFloat.minimumValue?.let { setMinValue(it) } + this@AnimatedFloat.maximumValue?.let { setMaxValue(it) } + this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it } + } } fun snapTo(newPosition: Float) { @@ -233,11 +239,10 @@ class BackPanel( snapTo(restingPosition) } - fun stretchTo( - stretchAmount: Float, - startingVelocity: Float? = null, - springForce: SpringForce? = null + stretchAmount: Float, + startingVelocity: Float? = null, + springForce: SpringForce? = null ) { animation.apply { startingVelocity?.let { @@ -297,8 +302,8 @@ class BackPanel( } fun addAnimationEndListener( - animatedFloat: AnimatedFloat, - endListener: DelayedOnAnimationEndListener + animatedFloat: AnimatedFloat, + endListener: DelayedOnAnimationEndListener ): Boolean { return if (animatedFloat.isRunning) { animatedFloat.addEndListener(endListener) @@ -314,51 +319,51 @@ class BackPanel( } fun setStretch( - horizontalTranslationStretchAmount: Float, - arrowStretchAmount: Float, - arrowAlphaStretchAmount: Float, - backgroundAlphaStretchAmount: Float, - backgroundWidthStretchAmount: Float, - backgroundHeightStretchAmount: Float, - edgeCornerStretchAmount: Float, - farCornerStretchAmount: Float, - fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens + horizontalTranslationStretchAmount: Float, + arrowStretchAmount: Float, + arrowAlphaStretchAmount: Float, + backgroundAlphaStretchAmount: Float, + backgroundWidthStretchAmount: Float, + backgroundHeightStretchAmount: Float, + edgeCornerStretchAmount: Float, + farCornerStretchAmount: Float, + fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens ) { horizontalTranslation.stretchBy( - finalPosition = fullyStretchedDimens.horizontalTranslation, - amount = horizontalTranslationStretchAmount + finalPosition = fullyStretchedDimens.horizontalTranslation, + amount = horizontalTranslationStretchAmount ) arrowLength.stretchBy( - finalPosition = fullyStretchedDimens.arrowDimens.length, - amount = arrowStretchAmount + finalPosition = fullyStretchedDimens.arrowDimens.length, + amount = arrowStretchAmount ) arrowHeight.stretchBy( - finalPosition = fullyStretchedDimens.arrowDimens.height, - amount = arrowStretchAmount + finalPosition = fullyStretchedDimens.arrowDimens.height, + amount = arrowStretchAmount ) arrowAlpha.stretchBy( - finalPosition = fullyStretchedDimens.arrowDimens.alpha, - amount = arrowAlphaStretchAmount + finalPosition = fullyStretchedDimens.arrowDimens.alpha, + amount = arrowAlphaStretchAmount ) backgroundAlpha.stretchBy( - finalPosition = fullyStretchedDimens.backgroundDimens.alpha, - amount = backgroundAlphaStretchAmount + finalPosition = fullyStretchedDimens.backgroundDimens.alpha, + amount = backgroundAlphaStretchAmount ) backgroundWidth.stretchBy( - finalPosition = fullyStretchedDimens.backgroundDimens.width, - amount = backgroundWidthStretchAmount + finalPosition = fullyStretchedDimens.backgroundDimens.width, + amount = backgroundWidthStretchAmount ) backgroundHeight.stretchBy( - finalPosition = fullyStretchedDimens.backgroundDimens.height, - amount = backgroundHeightStretchAmount + finalPosition = fullyStretchedDimens.backgroundDimens.height, + amount = backgroundHeightStretchAmount ) backgroundEdgeCornerRadius.stretchBy( - finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius, - amount = edgeCornerStretchAmount + finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius, + amount = edgeCornerStretchAmount ) backgroundFarCornerRadius.stretchBy( - finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius, - amount = farCornerStretchAmount + finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius, + amount = farCornerStretchAmount ) } @@ -373,8 +378,11 @@ class BackPanel( } fun popArrowAlpha(startingVelocity: Float, springForce: SpringForce? = null) { - arrowAlpha.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity, - springForce = springForce) + arrowAlpha.stretchTo( + stretchAmount = 0f, + startingVelocity = startingVelocity, + springForce = springForce + ) } fun resetStretch() { @@ -392,12 +400,10 @@ class BackPanel( backgroundFarCornerRadius.snapToRestingPosition() } - /** - * Updates resting arrow and background size not accounting for stretch - */ + /** Updates resting arrow and background size not accounting for stretch */ internal fun setRestingDimens( - restingParams: EdgePanelParams.BackIndicatorDimens, - animate: Boolean = true + restingParams: EdgePanelParams.BackIndicatorDimens, + animate: Boolean = true ) { horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation) scale.updateRestingPosition(restingParams.scale) @@ -410,27 +416,29 @@ class BackPanel( backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate) backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate) backgroundEdgeCornerRadius.updateRestingPosition( - restingParams.backgroundDimens.edgeCornerRadius, animate + restingParams.backgroundDimens.edgeCornerRadius, + animate ) backgroundFarCornerRadius.updateRestingPosition( - restingParams.backgroundDimens.farCornerRadius, animate + restingParams.backgroundDimens.farCornerRadius, + animate ) } fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos) fun setSpring( - horizontalTranslation: SpringForce? = null, - verticalTranslation: SpringForce? = null, - scale: SpringForce? = null, - arrowLength: SpringForce? = null, - arrowHeight: SpringForce? = null, - arrowAlpha: SpringForce? = null, - backgroundAlpha: SpringForce? = null, - backgroundFarCornerRadius: SpringForce? = null, - backgroundEdgeCornerRadius: SpringForce? = null, - backgroundWidth: SpringForce? = null, - backgroundHeight: SpringForce? = null, + horizontalTranslation: SpringForce? = null, + verticalTranslation: SpringForce? = null, + scale: SpringForce? = null, + arrowLength: SpringForce? = null, + arrowHeight: SpringForce? = null, + arrowAlpha: SpringForce? = null, + backgroundAlpha: SpringForce? = null, + backgroundFarCornerRadius: SpringForce? = null, + backgroundEdgeCornerRadius: SpringForce? = null, + backgroundWidth: SpringForce? = null, + backgroundHeight: SpringForce? = null, ) { arrowLength?.let { this.arrowLength.spring = it } arrowHeight?.let { this.arrowHeight.spring = it } @@ -459,26 +467,28 @@ class BackPanel( if (!isLeftPanel) canvas.scale(-1f, 1f, canvasWidth / 2.0f, 0f) - canvas.translate( - horizontalTranslation.pos, - height * 0.5f + verticalTranslation.pos - ) + canvas.translate(horizontalTranslation.pos, height * 0.5f + verticalTranslation.pos) canvas.scale(scale.pos, scale.pos, scalePivotX, 0f) - val arrowBackground = arrowBackgroundRect.apply { - left = 0f - top = -halfHeight - right = backgroundWidth - bottom = halfHeight - }.toPathWithRoundCorners( - topLeft = edgeCorner, - bottomLeft = edgeCorner, - topRight = farCorner, - bottomRight = farCorner + val arrowBackground = + arrowBackgroundRect + .apply { + left = 0f + top = -halfHeight + right = backgroundWidth + bottom = halfHeight + } + .toPathWithRoundCorners( + topLeft = edgeCorner, + bottomLeft = edgeCorner, + topRight = farCorner, + bottomRight = farCorner + ) + canvas.drawPath( + arrowBackground, + arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() } ) - canvas.drawPath(arrowBackground, - arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() }) val dx = arrowLength.pos val dy = arrowHeight.pos @@ -487,8 +497,8 @@ class BackPanel( // either the tip or the back of the arrow, whichever is closer val arrowOffset = (backgroundWidth - dx) / 2 canvas.translate( - /* dx= */ arrowOffset, - /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */ + /* dx= */ arrowOffset, + /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */ ) val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel) @@ -500,8 +510,8 @@ class BackPanel( } val arrowPath = calculateArrowPath(dx = dx, dy = dy) - val arrowPaint = arrowPaint - .apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() } + val arrowPaint = + arrowPaint.apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() } canvas.drawPath(arrowPath, arrowPaint) canvas.restore() @@ -519,17 +529,23 @@ class BackPanel( } private fun RectF.toPathWithRoundCorners( - topLeft: Float = 0f, - topRight: Float = 0f, - bottomRight: Float = 0f, - bottomLeft: Float = 0f - ): Path = Path().apply { - val corners = floatArrayOf( - topLeft, topLeft, - topRight, topRight, - bottomRight, bottomRight, - bottomLeft, bottomLeft - ) - addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW) - } -}
\ No newline at end of file + topLeft: Float = 0f, + topRight: Float = 0f, + bottomRight: Float = 0f, + bottomLeft: Float = 0f + ): Path = + Path().apply { + val corners = + floatArrayOf( + topLeft, + topLeft, + topRight, + topRight, + bottomRight, + bottomRight, + bottomLeft, + bottomLeft + ) + addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index f8086f5f6fb4..b208434c3218 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -27,7 +27,6 @@ import android.view.Gravity import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.VelocityTracker -import android.view.View import android.view.ViewConfiguration import android.view.WindowManager import androidx.annotation.VisibleForTesting @@ -164,6 +163,7 @@ internal constructor( private val elapsedTimeSinceInactive get() = systemClock.uptimeMillis() - gestureInactiveTime + private val elapsedTimeSinceEntry get() = systemClock.uptimeMillis() - gestureEntryTime @@ -612,6 +612,7 @@ internal constructor( } private var previousPreThresholdWidthInterpolator = params.entryWidthInterpolator + private fun preThresholdWidthStretchAmount(progress: Float): Float { val interpolator = run { val isPastSlop = totalTouchDeltaInactive > viewConfiguration.scaledTouchSlop @@ -677,8 +678,7 @@ internal constructor( velocityTracker?.run { computeCurrentVelocity(PX_PER_SEC) xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1) - } - ?: 0f + } ?: 0f val isPastFlingVelocityThreshold = flingVelocity > viewConfiguration.scaledMinimumFlingVelocity return flingDistance > minFlingDistance && isPastFlingVelocityThreshold @@ -1006,15 +1006,15 @@ internal constructor( private fun performDeactivatedHapticFeedback() { vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE ) } private fun performActivatedHapticFeedback() { vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE ) } @@ -1028,8 +1028,7 @@ internal constructor( velocityTracker?.run { computeCurrentVelocity(PX_PER_MS) MathUtils.smoothStep(slowVelocityBound, fastVelocityBound, abs(xVelocity)) - } - ?: valueOnFastVelocity + } ?: valueOnFastVelocity return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor) } 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/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index 439b7e18e0df..db8749f59d9c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -10,92 +10,114 @@ import com.android.systemui.res.R data class EdgePanelParams(private var resources: Resources) { data class ArrowDimens( - val length: Float? = 0f, - val height: Float? = 0f, - val alpha: Float = 0f, - val heightSpring: SpringForce? = null, - val lengthSpring: SpringForce? = null, - var alphaSpring: Step<SpringForce>? = null, - var alphaInterpolator: Step<Float>? = null + val length: Float? = 0f, + val height: Float? = 0f, + val alpha: Float = 0f, + val heightSpring: SpringForce? = null, + val lengthSpring: SpringForce? = null, + var alphaSpring: Step<SpringForce>? = null, + var alphaInterpolator: Step<Float>? = null ) data class BackgroundDimens( - val width: Float? = 0f, - val height: Float = 0f, - val edgeCornerRadius: Float = 0f, - val farCornerRadius: Float = 0f, - val alpha: Float = 0f, - val widthSpring: SpringForce? = null, - val heightSpring: SpringForce? = null, - val farCornerRadiusSpring: SpringForce? = null, - val edgeCornerRadiusSpring: SpringForce? = null, - val alphaSpring: SpringForce? = null, + val width: Float? = 0f, + val height: Float = 0f, + val edgeCornerRadius: Float = 0f, + val farCornerRadius: Float = 0f, + val alpha: Float = 0f, + val widthSpring: SpringForce? = null, + val heightSpring: SpringForce? = null, + val farCornerRadiusSpring: SpringForce? = null, + val edgeCornerRadiusSpring: SpringForce? = null, + val alphaSpring: SpringForce? = null, ) data class BackIndicatorDimens( - val horizontalTranslation: Float? = 0f, - val scale: Float = 0f, - val scalePivotX: Float? = null, - val arrowDimens: ArrowDimens, - val backgroundDimens: BackgroundDimens, - val verticalTranslationSpring: SpringForce? = null, - val horizontalTranslationSpring: SpringForce? = null, - val scaleSpring: SpringForce? = null, + val horizontalTranslation: Float? = 0f, + val scale: Float = 0f, + val scalePivotX: Float? = null, + val arrowDimens: ArrowDimens, + val backgroundDimens: BackgroundDimens, + val verticalTranslationSpring: SpringForce? = null, + val horizontalTranslationSpring: SpringForce? = null, + val scaleSpring: SpringForce? = null, ) lateinit var entryIndicator: BackIndicatorDimens private set + lateinit var activeIndicator: BackIndicatorDimens private set + lateinit var cancelledIndicator: BackIndicatorDimens private set + lateinit var flungIndicator: BackIndicatorDimens private set + lateinit var committedIndicator: BackIndicatorDimens private set + lateinit var preThresholdIndicator: BackIndicatorDimens private set + lateinit var fullyStretchedIndicator: BackIndicatorDimens private set // navigation bar edge constants var arrowPaddingEnd: Int = 0 private set + var arrowThickness: Float = 0f private set + // The closest to y var minArrowYPosition: Int = 0 private set + var fingerOffset: Int = 0 private set + var staticTriggerThreshold: Float = 0f private set + var reactivationTriggerThreshold: Float = 0f private set + var deactivationTriggerThreshold: Float = 0f get() = -field private set + lateinit var dynamicTriggerThresholdRange: ClosedRange<Float> private set + var swipeProgressThreshold: Float = 0f private set lateinit var entryWidthInterpolator: Interpolator private set + lateinit var entryWidthTowardsEdgeInterpolator: Interpolator private set + lateinit var activeWidthInterpolator: Interpolator private set + lateinit var arrowAngleInterpolator: Interpolator private set + lateinit var horizontalTranslationInterpolator: Interpolator private set + lateinit var verticalTranslationInterpolator: Interpolator private set + lateinit var farCornerInterpolator: Interpolator private set + lateinit var edgeCornerInterpolator: Interpolator private set + lateinit var heightInterpolator: Interpolator private set @@ -108,7 +130,10 @@ data class EdgePanelParams(private var resources: Resources) { } private fun getDimenFloat(id: Int): Float { - return TypedValue().run { resources.getValue(id, this, true); float } + return TypedValue().run { + resources.getValue(id, this, true) + float + } } private fun getPx(id: Int): Int { @@ -123,11 +148,10 @@ data class EdgePanelParams(private var resources: Resources) { fingerOffset = getPx(R.dimen.navigation_edge_finger_offset) staticTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold) reactivationTriggerThreshold = - getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold) + getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold) deactivationTriggerThreshold = - getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold) - dynamicTriggerThresholdRange = - reactivationTriggerThreshold..deactivationTriggerThreshold + getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold) + dynamicTriggerThresholdRange = reactivationTriggerThreshold..deactivationTriggerThreshold swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold) entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f) @@ -149,27 +173,31 @@ data class EdgePanelParams(private var resources: Resources) { val commonArrowDimensAlphaThreshold = .165f val commonArrowDimensAlphaFactor = 1.05f - val commonArrowDimensAlphaSpring = Step( - threshold = commonArrowDimensAlphaThreshold, - factor = commonArrowDimensAlphaFactor, - postThreshold = createSpring(180f, 0.9f), - preThreshold = createSpring(2000f, 0.6f) - ) - val commonArrowDimensAlphaSpringInterpolator = Step( - threshold = commonArrowDimensAlphaThreshold, - factor = commonArrowDimensAlphaFactor, - postThreshold = 1f, - preThreshold = 0f - ) - - entryIndicator = BackIndicatorDimens( + val commonArrowDimensAlphaSpring = + Step( + threshold = commonArrowDimensAlphaThreshold, + factor = commonArrowDimensAlphaFactor, + postThreshold = createSpring(180f, 0.9f), + preThreshold = createSpring(2000f, 0.6f) + ) + val commonArrowDimensAlphaSpringInterpolator = + Step( + threshold = commonArrowDimensAlphaThreshold, + factor = commonArrowDimensAlphaFactor, + postThreshold = 1f, + preThreshold = 0f + ) + + entryIndicator = + BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin), scale = getDimenFloat(R.dimen.navigation_edge_entry_scale), scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width), horizontalTranslationSpring = createSpring(800f, 0.76f), verticalTranslationSpring = createSpring(30000f, 1f), scaleSpring = createSpring(120f, 0.8f), - arrowDimens = ArrowDimens( + arrowDimens = + ArrowDimens( length = getDimen(R.dimen.navigation_edge_entry_arrow_length), height = getDimen(R.dimen.navigation_edge_entry_arrow_height), alpha = 0f, @@ -177,8 +205,9 @@ data class EdgePanelParams(private var resources: Resources) { heightSpring = createSpring(600f, 0.4f), alphaSpring = commonArrowDimensAlphaSpring, alphaInterpolator = commonArrowDimensAlphaSpringInterpolator - ), - backgroundDimens = BackgroundDimens( + ), + backgroundDimens = + BackgroundDimens( alpha = 1f, width = getDimen(R.dimen.navigation_edge_entry_background_width), height = getDimen(R.dimen.navigation_edge_entry_background_height), @@ -188,16 +217,18 @@ data class EdgePanelParams(private var resources: Resources) { heightSpring = createSpring(1500f, 0.45f), farCornerRadiusSpring = createSpring(300f, 0.5f), edgeCornerRadiusSpring = createSpring(150f, 0.5f), - ) - ) + ) + ) - activeIndicator = BackIndicatorDimens( + activeIndicator = + BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin), scale = getDimenFloat(R.dimen.navigation_edge_active_scale), horizontalTranslationSpring = createSpring(1000f, 0.8f), scaleSpring = createSpring(325f, 0.55f), scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width), - arrowDimens = ArrowDimens( + arrowDimens = + ArrowDimens( length = getDimen(R.dimen.navigation_edge_active_arrow_length), height = getDimen(R.dimen.navigation_edge_active_arrow_height), alpha = 1f, @@ -205,8 +236,9 @@ data class EdgePanelParams(private var resources: Resources) { heightSpring = activeCommittedArrowHeightSpring, alphaSpring = commonArrowDimensAlphaSpring, alphaInterpolator = commonArrowDimensAlphaSpringInterpolator - ), - backgroundDimens = BackgroundDimens( + ), + backgroundDimens = + BackgroundDimens( alpha = 1f, width = getDimen(R.dimen.navigation_edge_active_background_width), height = getDimen(R.dimen.navigation_edge_active_background_height), @@ -216,16 +248,18 @@ data class EdgePanelParams(private var resources: Resources) { heightSpring = createSpring(10000f, 1f), edgeCornerRadiusSpring = createSpring(2600f, 0.855f), farCornerRadiusSpring = createSpring(1200f, 0.30f), - ) - ) + ) + ) - preThresholdIndicator = BackIndicatorDimens( + preThresholdIndicator = + BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin), scale = getDimenFloat(R.dimen.navigation_edge_pre_threshold_scale), scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width), scaleSpring = createSpring(120f, 0.8f), horizontalTranslationSpring = createSpring(6000f, 1f), - arrowDimens = ArrowDimens( + arrowDimens = + ArrowDimens( length = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_length), height = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_height), alpha = 1f, @@ -233,32 +267,36 @@ data class EdgePanelParams(private var resources: Resources) { heightSpring = createSpring(100f, 0.6f), alphaSpring = commonArrowDimensAlphaSpring, alphaInterpolator = commonArrowDimensAlphaSpringInterpolator - ), - backgroundDimens = BackgroundDimens( + ), + backgroundDimens = + BackgroundDimens( alpha = 1f, width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width), height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height), edgeCornerRadius = - getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners), + getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners), farCornerRadius = - getDimen(R.dimen.navigation_edge_pre_threshold_far_corners), + getDimen(R.dimen.navigation_edge_pre_threshold_far_corners), widthSpring = createSpring(650f, 1f), heightSpring = createSpring(1500f, 0.45f), farCornerRadiusSpring = createSpring(300f, 1f), edgeCornerRadiusSpring = createSpring(250f, 0.5f), - ) - ) + ) + ) - committedIndicator = activeIndicator.copy( + committedIndicator = + activeIndicator.copy( horizontalTranslation = null, scalePivotX = null, - arrowDimens = activeIndicator.arrowDimens.copy( + arrowDimens = + activeIndicator.arrowDimens.copy( lengthSpring = activeCommittedArrowLengthSpring, heightSpring = activeCommittedArrowHeightSpring, length = null, height = null, - ), - backgroundDimens = activeIndicator.backgroundDimens.copy( + ), + backgroundDimens = + activeIndicator.backgroundDimens.copy( alpha = 0f, // explicitly set to null to preserve previous width upon state change width = null, @@ -267,49 +305,57 @@ data class EdgePanelParams(private var resources: Resources) { edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring, farCornerRadiusSpring = flungCommittedFarCornerSpring, alphaSpring = createSpring(1400f, 1f), - ), + ), scale = 0.86f, scaleSpring = createSpring(5700f, 1f), - ) + ) - flungIndicator = committedIndicator.copy( - arrowDimens = committedIndicator.arrowDimens.copy( + flungIndicator = + committedIndicator.copy( + arrowDimens = + committedIndicator.arrowDimens.copy( lengthSpring = createSpring(850f, 0.46f), heightSpring = createSpring(850f, 0.46f), length = activeIndicator.arrowDimens.length, height = activeIndicator.arrowDimens.height - ), - backgroundDimens = committedIndicator.backgroundDimens.copy( + ), + backgroundDimens = + committedIndicator.backgroundDimens.copy( widthSpring = flungCommittedWidthSpring, heightSpring = flungCommittedHeightSpring, edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring, farCornerRadiusSpring = flungCommittedFarCornerSpring, - ) - ) + ) + ) - cancelledIndicator = entryIndicator.copy( - backgroundDimens = entryIndicator.backgroundDimens.copy( + cancelledIndicator = + entryIndicator.copy( + backgroundDimens = + entryIndicator.backgroundDimens.copy( width = 0f, alpha = 0f, alphaSpring = createSpring(450f, 1f) - ) - ) + ) + ) - fullyStretchedIndicator = BackIndicatorDimens( + fullyStretchedIndicator = + BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin), scale = getDimenFloat(R.dimen.navigation_edge_stretch_scale), horizontalTranslationSpring = null, verticalTranslationSpring = null, scaleSpring = null, - arrowDimens = ArrowDimens( + arrowDimens = + ArrowDimens( length = getDimen(R.dimen.navigation_edge_stretched_arrow_length), height = getDimen(R.dimen.navigation_edge_stretched_arrow_height), alpha = 1f, alphaSpring = null, heightSpring = null, lengthSpring = null, - ), - backgroundDimens = BackgroundDimens( + ), + backgroundDimens = + BackgroundDimens( alpha = 1f, width = getDimen(R.dimen.navigation_edge_stretch_background_width), height = getDimen(R.dimen.navigation_edge_stretch_background_height), @@ -320,11 +366,11 @@ data class EdgePanelParams(private var resources: Resources) { heightSpring = null, edgeCornerRadiusSpring = null, farCornerRadiusSpring = null, - ) - ) + ) + ) } } fun createSpring(stiffness: Float, dampingRatio: Float): SpringForce { return SpringForce().setStiffness(stiffness).setDampingRatio(dampingRatio) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt index deb0fed0ffc8..954e94af1c1a 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt @@ -26,11 +26,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.compose.theme.PlatformTheme -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.people.ui.compose.PeopleScreen -import com.android.systemui.people.ui.view.PeopleViewBinder -import com.android.systemui.people.ui.view.PeopleViewBinder.bind import com.android.systemui.people.ui.viewmodel.PeopleViewModel import javax.inject.Inject import kotlinx.coroutines.launch @@ -38,10 +34,7 @@ import kotlinx.coroutines.launch /** People Tile Widget configuration activity that shows the user their conversation tiles. */ class PeopleSpaceActivity @Inject -constructor( - private val viewModelFactory: PeopleViewModel.Factory, - private val featureFlags: FeatureFlags, -) : ComponentActivity() { +constructor(private val viewModelFactory: PeopleViewModel.Factory) : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) @@ -66,17 +59,7 @@ constructor( } // Set the content of the activity, using either the View or Compose implementation. - if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)) { - Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity") - setContent { - PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) } - } - } else { - Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity") - val view = PeopleViewBinder.create(this) - bind(view, viewModel, lifecycleOwner = this, onResult = { finishActivity(it) }) - setContentView(view) - } + setContent { PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) } } } private fun finishActivity(result: PeopleViewModel.Result) { diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java deleted file mode 100644 index 59c76adb721b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java +++ /dev/null @@ -1,81 +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.people; - -import android.app.people.PeopleSpaceTile; -import android.content.Context; -import android.content.pm.LauncherApps; -import android.graphics.Bitmap; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.systemui.res.R; - -/** - * PeopleSpaceTileView renders an individual person's tile with associated status. - */ -public class PeopleSpaceTileView extends LinearLayout { - - private View mTileView; - private TextView mNameView; - private ImageView mPersonIconView; - - public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId, boolean isLast) { - super(context); - mTileView = view.findViewWithTag(shortcutId); - if (mTileView == null) { - LayoutInflater inflater = LayoutInflater.from(context); - mTileView = inflater.inflate(R.layout.people_space_tile_view, view, false); - view.addView(mTileView, LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); - mTileView.setTag(shortcutId); - - // If it's not the last conversation in this section, add a divider. - if (!isLast) { - inflater.inflate(R.layout.people_space_activity_list_divider, view, true); - } - } - mNameView = mTileView.findViewById(R.id.tile_view_name); - mPersonIconView = mTileView.findViewById(R.id.tile_view_person_icon); - } - - /** Sets the name text on the tile. */ - public void setName(String name) { - mNameView.setText(name); - } - - /** Sets the person and package drawable on the tile. */ - public void setPersonIcon(Bitmap bitmap) { - mPersonIconView.setImageBitmap(bitmap); - } - - /** Sets the click listener of the tile. */ - public void setOnClickListener(LauncherApps launcherApps, PeopleSpaceTile tile) { - mTileView.setOnClickListener(v -> - launcherApps.startShortcut(tile.getPackageName(), tile.getId(), null, null, - tile.getUserHandle())); - } - - /** Sets the click listener of the tile directly. */ - public void setOnClickListener(OnClickListener onClickListener) { - mTileView.setOnClickListener(onClickListener); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt deleted file mode 100644 index 10a2b3ce7b85..000000000000 --- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.people.ui.view - -import android.content.Context -import android.graphics.Color -import android.graphics.Outline -import android.graphics.drawable.GradientDrawable -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.ViewOutlineProvider -import android.widget.LinearLayout -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.Lifecycle.State.CREATED -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.people.PeopleSpaceTileView -import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel -import com.android.systemui.people.ui.viewmodel.PeopleViewModel -import com.android.systemui.res.R -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch - -/** A ViewBinder for [PeopleViewModel]. */ -object PeopleViewBinder { - private const val TAG = "PeopleViewBinder" - - /** - * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists. - */ - private val ViewOutlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - 0, - 0, - view.width, - view.height, - view.context.resources.getDimension(R.dimen.people_space_widget_radius), - ) - } - } - - /** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */ - @JvmStatic - fun create(context: Context): ViewGroup { - return LayoutInflater.from(context) - .inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup - } - - /** Bind [view] to [viewModel]. */ - @JvmStatic - fun bind( - view: ViewGroup, - viewModel: PeopleViewModel, - lifecycleOwner: LifecycleOwner, - onResult: (PeopleViewModel.Result) -> Unit, - ) { - // Call [onResult] as soon as a result is available. - lifecycleOwner.lifecycleScope.launch { - lifecycleOwner.repeatOnLifecycle(CREATED) { - viewModel.result.collect { result -> - if (result != null) { - viewModel.clearResult() - onResult(result) - } - } - } - } - - // Start collecting the UI data once the Activity is STARTED. - lifecycleOwner.lifecycleScope.launch { - lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - combine( - viewModel.priorityTiles, - viewModel.recentTiles, - ) { priority, recent -> - priority to recent - } - .collect { (priorityTiles, recentTiles) -> - if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) { - setConversationsContent( - view, - priorityTiles, - recentTiles, - viewModel.onTileClicked, - ) - } else { - setNoConversationsContent(view, viewModel.onUserJourneyCancelled) - } - } - } - } - } - - private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) { - // This should never happen. - if (view.childCount > 1) { - error("view has ${view.childCount} children, it should have maximum 1") - } - - // The static content for no conversations is already shown. - if (view.findViewById<View>(R.id.top_level_no_conversations) != null) { - return - } - - // If we were showing the content with conversations earlier, remove it. - if (view.childCount == 1) { - view.removeViewAt(0) - } - - val context = view.context - val noConversationsView = - LayoutInflater.from(context) - .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view) - - noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener { - onGotItClicked() - } - - // The Tile preview has colorBackground as its background. Change it so it's different than - // the activity's background. - val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background) - val shape = item.background as GradientDrawable - val ta = - context.theme.obtainStyledAttributes( - intArrayOf(com.android.internal.R.attr.colorSurface) - ) - shape.setColor(ta.getColor(0, Color.WHITE)) - ta.recycle() - } - - private fun setConversationsContent( - view: ViewGroup, - priorityTiles: List<PeopleTileViewModel>, - recentTiles: List<PeopleTileViewModel>, - onTileClicked: (PeopleTileViewModel) -> Unit, - ) { - // This should never happen. - if (view.childCount > 1) { - error("view has ${view.childCount} children, it should have maximum 1") - } - - // Inflate the content with conversations, if it's not already. - if (view.findViewById<View>(R.id.top_level_with_conversations) == null) { - // If we were showing the content without conversations earlier, remove it. - if (view.childCount == 1) { - view.removeViewAt(0) - } - - LayoutInflater.from(view.context) - .inflate(R.layout.people_space_activity_with_conversations, /* root= */ view) - } - - // TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a - // single RecyclerView once this screen is tested by screenshot tests. Introduce a - // PeopleSpaceTileViewBinder that will properly create and bind the View associated to a - // PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class). - val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations) - setTileViews( - conversationsView, - R.id.priority, - R.id.priority_tiles, - priorityTiles, - onTileClicked, - ) - - setTileViews( - conversationsView, - R.id.recent, - R.id.recent_tiles, - recentTiles, - onTileClicked, - ) - } - - /** Sets a [PeopleSpaceTileView]s for each conversation. */ - private fun setTileViews( - root: View, - tilesListId: Int, - tilesId: Int, - tiles: List<PeopleTileViewModel>, - onTileClicked: (PeopleTileViewModel) -> Unit, - ) { - // Remove any previously added tile. - // TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use - // DiffUtil to do as less addView/removeView as possible. - val layout = root.requireViewById<ViewGroup>(tilesId) - layout.removeAllViews() - layout.outlineProvider = ViewOutlineProvider - - val tilesListView = root.requireViewById<LinearLayout>(tilesListId) - if (tiles.isEmpty()) { - tilesListView.visibility = View.GONE - return - } - tilesListView.visibility = View.VISIBLE - - // Add each tile. - tiles.forEachIndexed { i, tile -> - val tileView = - PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1) - bindTileView(tileView, tile, onTileClicked) - } - } - - /** Sets [tileView] with the data in [conversation]. */ - private fun bindTileView( - tileView: PeopleSpaceTileView, - tile: PeopleTileViewModel, - onTileClicked: (PeopleTileViewModel) -> Unit, - ) { - try { - tileView.setName(tile.username) - tileView.setPersonIcon(tile.icon) - tileView.setOnClickListener { onTileClicked(tile) } - } catch (e: Exception) { - Log.e(TAG, "Couldn't retrieve shortcut information", e) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index b34b3701528b..e77bd03b8af2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -485,6 +485,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } @Override + public int getMinRows() { + return mMinRows; + } + + @Override public boolean setMaxColumns(int maxColumns) { mMaxColumns = maxColumns; boolean changed = false; @@ -497,6 +502,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { return changed; } + @Override + public int getMaxColumns() { + return mMaxColumns; + } + /** * Set the amount of excess space that we gave this view compared to the actual available * height. This is because this view is in a scrollview. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 4ee2db796aef..cc0901fca822 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -307,7 +307,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } else { // Set the horizontal paddings unless the view is the Compose implementation of the // footer actions. - if (view.getTag(R.id.tag_compose_qs_footer_actions) == null) { + if (view.getId() != R.id.qs_footer_actions) { view.setPaddingRelative( mContentHorizontalPadding, view.getPaddingTop(), diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java index e4249757d737..38d7290fc3bc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java @@ -219,6 +219,13 @@ public class QSFragmentLegacy extends LifecycleFragment implements QS { } @Override + public void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) { + if (mQsImpl != null) { + mQsImpl.setShouldUpdateSquishinessOnMedia(shouldUpdate); + } + } + + @Override public void setListening(boolean listening) { if (mQsImpl != null) { mQsImpl.setListening(listening); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index 1f4838e85e79..8c0d122e5c00 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -34,11 +34,11 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import androidx.annotation.FloatRange; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.compose.ui.platform.ComposeView; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; @@ -48,15 +48,12 @@ import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.Dumpable; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSComponent; -import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; @@ -117,11 +114,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private final MediaHost mQqsMediaHost; private final QSDisableFlagsLogger mQsDisableFlagsLogger; private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - private final FeatureFlags mFeatureFlags; private final QSLogger mLogger; private final FooterActionsController mFooterActionsController; private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory; - private final FooterActionsViewBinder mFooterActionsViewBinder; private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner; private boolean mShowCollapsedOnKeyguard; private boolean mLastKeyguardAndExpanded; @@ -168,11 +163,14 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private boolean mIsSmallScreen; + /** Should the squishiness fraction be updated on the media host. */ + private boolean mShouldUpdateMediaSquishiness; + private CommandQueue mCommandQueue; private View mRootView; @Nullable - private View mFooterActionsView; + private ComposeView mFooterActionsView; @Inject public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, @@ -184,23 +182,19 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl DumpManager dumpManager, QSLogger qsLogger, FooterActionsController footerActionsController, FooterActionsViewModel.Factory footerActionsViewModelFactory, - FooterActionsViewBinder footerActionsViewBinder, - LargeScreenShadeInterpolator largeScreenShadeInterpolator, - FeatureFlags featureFlags) { + LargeScreenShadeInterpolator largeScreenShadeInterpolator) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mQsMediaHost = qsMediaHost; mQqsMediaHost = qqsMediaHost; mQsDisableFlagsLogger = qsDisableFlagsLogger; mLogger = qsLogger; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; - mFeatureFlags = featureFlags; mCommandQueue = commandQueue; mBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; mDumpManager = dumpManager; mFooterActionsController = footerActionsController; mFooterActionsViewModelFactory = footerActionsViewModelFactory; - mFooterActionsViewBinder = footerActionsViewBinder; mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); if (SceneContainerFlag.isEnabled()) { mStatusBarState = StatusBarState.SHADE; @@ -294,43 +288,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } private void bindFooterActionsView(View root) { - LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions); - - if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)) { - Log.d(TAG, "Binding the View implementation of the QS footer actions"); - mFooterActionsView = footerActionsView; - mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, - mListeningAndVisibilityLifecycleOwner); - return; - } - - // Compose is available, so let's use the Compose implementation of the footer actions. - Log.d(TAG, "Binding the Compose implementation of the QS footer actions"); - View composeView = QSUtils.createFooterActionsView(root.getContext(), + mFooterActionsView = root.findViewById(R.id.qs_footer_actions); + QSUtils.setFooterActionsViewContent(mFooterActionsView, mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); - mFooterActionsView = composeView; - - // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin - // to all views except for qs_footer_actions, so we set it to the Compose view. - composeView.setId(R.id.qs_footer_actions); - - // Set this tag so that QSContainerImpl does not add horizontal paddings to this Compose - // implementation of the footer actions. They will be set in Compose instead so that the - // background fills the full screen width. - composeView.setTag(R.id.tag_compose_qs_footer_actions, true); - - // Set the same elevation as the View implementation, otherwise the footer actions will be - // drawn below the scroll view with QS grid and clicks won't get through on small devices - // where there isn't enough vertical space to show all the tiles and the footer actions. - composeView.setElevation( - composeView.getContext().getResources().getDimension(R.dimen.qs_panel_elevation)); - - // Replace the View by the Compose provided one. - ViewGroup parent = (ViewGroup) footerActionsView.getParent(); - ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams(); - int index = parent.indexOfChild(footerActionsView); - parent.removeViewAt(index); - parent.addView(composeView, index, layoutParams); } @Override @@ -662,6 +622,12 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } @Override + public void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) { + if (DEBUG) Log.d(TAG, "setShouldUpdateSquishinessOnMedia " + shouldUpdate); + mShouldUpdateMediaSquishiness = shouldUpdate; + } + + @Override public void setQsExpansion(float expansion, float panelExpansionFraction, float proposedTranslation, float squishinessFraction) { float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; @@ -740,9 +706,11 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl if (mQSAnimator != null) { mQSAnimator.setPosition(expansion); } - if (!mInSplitShade + if (!mShouldUpdateMediaSquishiness + && (!mInSplitShade || mStatusBarStateController.getState() == KEYGUARD - || mStatusBarStateController.getState() == SHADE_LOCKED) { + || mStatusBarStateController.getState() == SHADE_LOCKED) + ) { // At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen // and media player expect no change by squishiness in lock screen shade. Don't bother // squishing mQsMediaHost when not in split shade to prevent problems with stale state. @@ -1038,6 +1006,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl indentingPw.println("mTransitioningToFullShade: " + mTransitioningToFullShade); indentingPw.println("mLockscreenToShadeProgress: " + mLockscreenToShadeProgress); indentingPw.println("mOverScrolling: " + mOverScrolling); + indentingPw.println("mShouldUpdateMediaSquishiness: " + mShouldUpdateMediaSquishiness); indentingPw.println("isCustomizing: " + mQSCustomizerController.isCustomizing()); View view = getView(); if (view != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 00757b7bd51a..9c8c17bb1ca0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -42,6 +42,7 @@ import com.android.internal.widget.RemeasuringLinearLayout; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -441,7 +442,7 @@ public class QSPanel extends LinearLayout implements Tunable { } private boolean needsDynamicRowsAndColumns() { - return true; + return !SceneContainerFlag.isEnabled(); } private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout) { @@ -634,8 +635,7 @@ public class QSPanel extends LinearLayout implements Tunable { switchAllContentToParent(newParent, mTileLayout); reAttachMediaHost(mediaHostView, horizontal); if (needsDynamicRowsAndColumns()) { - mTileLayout.setMinRows(horizontal ? 2 : 1); - mTileLayout.setMaxColumns(horizontal ? 2 : 4); + setColumnRowLayout(horizontal); } updateMargins(mediaHostView); if (mHorizontalLinearLayout != null) { @@ -644,6 +644,11 @@ public class QSPanel extends LinearLayout implements Tunable { } } + void setColumnRowLayout(boolean withMedia) { + mTileLayout.setMinRows(withMedia ? 2 : 1); + mTileLayout.setMaxColumns(withMedia ? 2 : 4); + } + private void updateMargins(ViewGroup mediaHostView) { updateMediaHostContentMargins(mediaHostView); updateHorizontalLinearLayoutMargins(); @@ -736,6 +741,8 @@ public class QSPanel extends LinearLayout implements Tunable { return false; } + int getMinRows(); + /** * Sets the max number of columns to show * @@ -747,6 +754,8 @@ public class QSPanel extends LinearLayout implements Tunable { return false; } + int getMaxColumns(); + /** * Sets the expansion value and proposedTranslation to panel. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index e24caf19a14b..f76183ed2d74 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -30,6 +30,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.dump.DumpManager; import com.android.systemui.haptics.qs.QSLongPressEffect; +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor; import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.media.controls.ui.view.MediaHostState; @@ -46,10 +47,13 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.tuner.TunerService; +import kotlinx.coroutines.flow.StateFlow; + import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; + /** * Controller for {@link QSPanel}. */ @@ -72,6 +76,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final BrightnessSliderController.Factory mBrightnessSliderControllerFactory; private final BrightnessController.Factory mBrightnessControllerFactory; + protected final MediaCarouselInteractor mMediaCarouselInteractor; + private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -94,7 +100,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { FalsingManager falsingManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, SplitShadeStateController splitShadeStateController, - Provider<QSLongPressEffect> longPRessEffectProvider) { + Provider<QSLongPressEffect> longPRessEffectProvider, + MediaCarouselInteractor mediaCarouselInteractor) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController, longPRessEffectProvider); @@ -113,6 +120,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLastDensity = view.getResources().getConfiguration().densityDpi; mSceneContainerEnabled = SceneContainerFlag.isEnabled(); + mMediaCarouselInteractor = mediaCarouselInteractor; } @Override @@ -126,6 +134,11 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { } @Override + StateFlow<Boolean> getMediaVisibleFlow() { + return mMediaCarouselInteractor.getHasAnyMediaOrRecommendation(); + } + + @Override protected void onViewAttached() { super.onViewAttached(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 583cfb9ab47e..3b5cc61057e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -41,13 +41,17 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileViewImpl; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.animation.DisappearParameters; +import com.android.systemui.util.kotlin.JavaAdapterKt; import kotlin.Unit; import kotlin.jvm.functions.Function1; +import kotlinx.coroutines.flow.StateFlow; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; @@ -58,6 +62,7 @@ import java.util.stream.Collectors; import javax.inject.Provider; + /** * Controller for QSPanel views. * @@ -93,6 +98,15 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private final Provider<QSLongPressEffect> mLongPressEffectProvider; + private boolean mDestroyed = false; + + private boolean mMediaVisibleFromInteractor; + + private final Consumer<Boolean> mMediaOrRecommendationVisibleConsumer = mediaVisible -> { + mMediaVisibleFromInteractor = mediaVisible; + setLayoutForMediaInScene(); + }; + @VisibleForTesting protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -115,7 +129,11 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr /* newScreenLayout= */ mLastScreenLayout, /* containerName= */ mView.getDumpableTag()); - switchTileLayoutIfNeeded(); + if (SceneContainerFlag.isEnabled()) { + setLayoutForMediaInScene(); + } else { + switchTileLayoutIfNeeded(); + } onConfigurationChanged(); if (previousSplitShadeState != mShouldUseSplitNotificationShade) { onSplitShadeChanged(mShouldUseSplitNotificationShade); @@ -173,6 +191,9 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mView.initialize(mQSLogger, mUsingMediaPlayer); mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), ""); mHost.addCallback(mQSHostCallback); + if (SceneContainerFlag.isEnabled()) { + registerForMediaInteractorChanges(); + } } /** @@ -192,7 +213,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr // will remove the attach listener. We don't need to do that, because once this object is // detached from the graph, it will be gc. mHost.removeCallback(mQSHostCallback); - + mDestroyed = true; for (TileRecord record : mRecords) { record.tile.removeCallback(record.callback); mView.removeTile(record); @@ -207,17 +228,32 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mQsTileRevealController.setExpansion(mRevealExpansion); } - mMediaHost.addVisibilityChangeListener(mMediaHostVisibilityListener); + if (!SceneContainerFlag.isEnabled()) { + mMediaHost.addVisibilityChangeListener(mMediaHostVisibilityListener); + } mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener); setTiles(); mLastOrientation = getResources().getConfiguration().orientation; mLastScreenLayout = getResources().getConfiguration().screenLayout; mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag()); + if (SceneContainerFlag.isEnabled()) { + setLayoutForMediaInScene(); + } switchTileLayout(true); mDumpManager.registerDumpable(mView.getDumpableTag(), this); } + private void registerForMediaInteractorChanges() { + JavaAdapterKt.collectFlow( + mView, + getMediaVisibleFlow(), + mMediaOrRecommendationVisibleConsumer + ); + } + + abstract StateFlow<Boolean> getMediaVisibleFlow(); + @Override protected void onViewDetached() { mQSLogger.logOnViewDetached(mLastOrientation, mView.getDumpableTag()); @@ -242,6 +278,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr /** */ public void setTiles(Collection<QSTile> tiles, boolean collapsedView) { + if (mDestroyed) return; // TODO(b/168904199): move this logic into QSPanelController. if (!collapsedView && mQsTileRevealController != null) { mQsTileRevealController.updateRevealedTiles(tiles); @@ -433,6 +470,11 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr return false; } + void setLayoutForMediaInScene() { + boolean withMedia = shouldUseHorizontalInScene(); + mView.setColumnRowLayout(withMedia); + } + /** * Update the way the media disappears based on if we're using the horizontal layout */ @@ -473,6 +515,16 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr == Configuration.SCREENLAYOUT_LONG_YES; } + boolean shouldUseHorizontalInScene() { + if (mShouldUseSplitNotificationShade) { + return false; + } + return mMediaVisibleFromInteractor + && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE + && (mLastScreenLayout & Configuration.SCREENLAYOUT_LONG_MASK) + == Configuration.SCREENLAYOUT_LONG_YES; + } + private void logTiles() { for (int i = 0; i < mRecords.size(); i++) { QSTile tile = mRecords.get(i).tile; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt index 15c3f271469d..5482e6da9a57 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt @@ -1,10 +1,9 @@ package com.android.systemui.qs import android.content.Context -import android.view.View +import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.LifecycleOwner import com.android.compose.theme.PlatformTheme -import com.android.compose.ui.platform.DensityAwareComposeView import com.android.internal.policy.SystemBarUtils import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel @@ -29,13 +28,11 @@ object QSUtils { } @JvmStatic - fun createFooterActionsView( - context: Context, + fun setFooterActionsViewContent( + view: ComposeView, viewModel: FooterActionsViewModel, qsVisibilityLifecycleOwner: LifecycleOwner, - ): View { - return DensityAwareComposeView(context).apply { - setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } - } + ) { + view.setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 6cda740dd1a8..f207b1de3cba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -26,6 +26,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.dump.DumpManager; import com.android.systemui.haptics.qs.QSLongPressEffect; +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor; import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.plugins.qs.QSTile; @@ -36,6 +37,8 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.leak.RotationUtils; +import kotlinx.coroutines.flow.StateFlow; + import java.util.ArrayList; import java.util.List; @@ -43,12 +46,15 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; + /** Controller for {@link QuickQSPanel}. */ @QSScope public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> { private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider; + private final MediaCarouselInteractor mMediaCarouselInteractor; + @Inject QuickQSPanelController(QuickQSPanel view, QSHost qsHost, QSCustomizerController qsCustomizerController, @@ -58,12 +64,14 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> Provider<Boolean> usingCollapsedLandscapeMediaProvider, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager, SplitShadeStateController splitShadeStateController, - Provider<QSLongPressEffect> longPressEffectProvider + Provider<QSLongPressEffect> longPressEffectProvider, + MediaCarouselInteractor mediaCarouselInteractor ) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController, longPressEffectProvider); mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider; + mMediaCarouselInteractor = mediaCarouselInteractor; } @Override @@ -74,6 +82,11 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); } + @Override + StateFlow<Boolean> getMediaVisibleFlow() { + return mMediaCarouselInteractor.getHasActiveMediaOrRecommendation(); + } + private void updateMediaExpansion() { int rotation = getRotation(); boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index dcb9288f74be..ef44e5fb8757 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -12,6 +12,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.FontSizeUtils; @@ -97,12 +98,24 @@ public class TileLayout extends ViewGroup implements QSTileLayout { return false; } + @VisibleForTesting + @Override + public int getMinRows() { + return mMinRows; + } + @Override public boolean setMaxColumns(int maxColumns) { mMaxColumns = maxColumns; return updateColumns(); } + @VisibleForTesting + @Override + public int getMaxColumns() { + return mMaxColumns; + } + public void addTile(TileRecord tile) { mRecords.add(tile); tile.tile.setListening(this, mListening); diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt deleted file mode 100644 index 0995dd4e592e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.footer.ui.binder - -import android.content.Context -import android.graphics.PorterDuff -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import androidx.core.view.isVisible -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.animation.Expandable -import com.android.systemui.common.ui.binder.IconViewBinder -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.people.ui.view.PeopleViewBinder.bind -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.res.R -import javax.inject.Inject -import kotlin.math.roundToInt -import kotlinx.coroutines.launch - -/** A ViewBinder for [FooterActionsViewBinder]. */ -@SysUISingleton -class FooterActionsViewBinder @Inject constructor() { - /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */ - fun create(context: Context): LinearLayout { - return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null) - as LinearLayout - } - - /** Bind [view] to [viewModel]. */ - fun bind( - view: LinearLayout, - viewModel: FooterActionsViewModel, - qsVisibilityLifecycleOwner: LifecycleOwner, - ) { - view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES - - // Add the views used by this new implementation. - val context = view.context - val inflater = LayoutInflater.from(context) - - val securityHolder = TextButtonViewHolder.createAndAdd(inflater, view) - val foregroundServicesWithTextHolder = TextButtonViewHolder.createAndAdd(inflater, view) - val foregroundServicesWithNumberHolder = NumberButtonViewHolder.createAndAdd(inflater, view) - val userSwitcherHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = false) - val settingsHolder = - IconButtonViewHolder.createAndAdd(inflater, view, isLast = viewModel.power == null) - - // Bind the static power and settings buttons. - bindButton(settingsHolder, viewModel.settings) - - if (viewModel.power != null) { - val powerHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = true) - bindButton(powerHolder, viewModel.power) - } - - // There are 2 lifecycle scopes we are using here: - // 1) The scope created by [repeatWhenAttached] when [view] is attached, and destroyed - // when the [view] is detached. We use this as the parent scope for all our [viewModel] - // state collection, given that we don't want to do any work when [view] is detached. - // 2) The scope owned by [lifecycleOwner], which should be RESUMED only when Quick - // Settings are visible. We use this to make sure we collect UI state only when the - // View is visible. - // - // Given that we start our collection when the Quick Settings become visible, which happens - // every time the user swipes down the shade, we remember our previous UI state already - // bound to the UI to avoid binding the same values over and over for nothing. - - // TODO(b/242040009): Look into using only a single scope. - - var previousSecurity: FooterActionsSecurityButtonViewModel? = null - var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null - var previousUserSwitcher: FooterActionsButtonViewModel? = null - - // Listen for ViewModel updates when the View is attached. - view.repeatWhenAttached { - val attachedScope = this.lifecycleScope - - attachedScope.launch { - // Listen for dialog requests as soon as we are attached, even when not visible. - // TODO(b/242040009): Should this move somewhere else? - launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) } - - // Make sure we set the correct alphas even when QS are not currently shown. - launch { viewModel.alpha.collect { view.alpha = it } } - launch { - viewModel.backgroundAlpha.collect { - view.background?.alpha = (it * 255).roundToInt() - } - } - } - - // Listen for model changes only when QS are visible. - qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - // Security. - launch { - viewModel.security.collect { security -> - if (previousSecurity != security) { - bindSecurity(view.context, securityHolder, security) - previousSecurity = security - } - } - } - - // Foreground services. - launch { - viewModel.foregroundServices.collect { foregroundServices -> - if (previousForegroundServices != foregroundServices) { - bindForegroundService( - foregroundServicesWithNumberHolder, - foregroundServicesWithTextHolder, - foregroundServices, - ) - previousForegroundServices = foregroundServices - } - } - } - - // User switcher. - launch { - viewModel.userSwitcher.collect { userSwitcher -> - if (previousUserSwitcher != userSwitcher) { - bindButton(userSwitcherHolder, userSwitcher) - previousUserSwitcher = userSwitcher - } - } - } - } - } - } - - private fun bindSecurity( - quickSettingsContext: Context, - securityHolder: TextButtonViewHolder, - security: FooterActionsSecurityButtonViewModel?, - ) { - val securityView = securityHolder.view - securityView.isVisible = security != null - if (security == null) { - return - } - - // Make sure that the chevron is visible and that the button is clickable if there is a - // listener. - val chevron = securityHolder.chevron - val onClick = security.onClick - if (onClick != null) { - securityView.isClickable = true - securityView.setOnClickListener { - onClick(quickSettingsContext, Expandable.fromView(securityView)) - } - chevron.isVisible = true - } else { - securityView.isClickable = false - securityView.setOnClickListener(null) - chevron.isVisible = false - } - - securityHolder.text.text = security.text - securityHolder.newDot.isVisible = false - IconViewBinder.bind(security.icon, securityHolder.icon) - } - - private fun bindForegroundService( - foregroundServicesWithNumberHolder: NumberButtonViewHolder, - foregroundServicesWithTextHolder: TextButtonViewHolder, - foregroundServices: FooterActionsForegroundServicesButtonViewModel?, - ) { - val foregroundServicesWithNumberView = foregroundServicesWithNumberHolder.view - val foregroundServicesWithTextView = foregroundServicesWithTextHolder.view - if (foregroundServices == null) { - foregroundServicesWithNumberView.isVisible = false - foregroundServicesWithTextView.isVisible = false - return - } - - val foregroundServicesCount = foregroundServices.foregroundServicesCount - if (foregroundServices.displayText) { - // Button with text, icon and chevron. - foregroundServicesWithNumberView.isVisible = false - - foregroundServicesWithTextView.isVisible = true - foregroundServicesWithTextView.setOnClickListener { - foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView)) - } - foregroundServicesWithTextHolder.text.text = foregroundServices.text - foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges - } else { - // Small button with the number only. - foregroundServicesWithTextView.isVisible = false - - foregroundServicesWithNumberView.isVisible = true - foregroundServicesWithNumberView.setOnClickListener { - foregroundServices.onClick(Expandable.fromView(foregroundServicesWithNumberView)) - } - foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString() - foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text - foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges - } - } - - private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) { - val buttonView = button.view - buttonView.id = model?.id ?: View.NO_ID - buttonView.isVisible = model != null - if (model == null) { - return - } - - val backgroundResource = - when (model.backgroundColor) { - R.attr.shadeInactive -> R.drawable.qs_footer_action_circle - R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color - else -> error("Unsupported icon background resource ${model.backgroundColor}") - } - buttonView.setBackgroundResource(backgroundResource) - buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) } - - val icon = model.icon - val iconView = button.icon - - IconViewBinder.bind(icon, iconView) - if (model.iconTint != null) { - iconView.setColorFilter(model.iconTint, PorterDuff.Mode.SRC_IN) - } else { - iconView.clearColorFilter() - } - } -} - -private class TextButtonViewHolder(val view: View) { - val icon = view.requireViewById<ImageView>(R.id.icon) - val text = view.requireViewById<TextView>(R.id.text) - val newDot = view.requireViewById<ImageView>(R.id.new_dot) - val chevron = view.requireViewById<ImageView>(R.id.chevron_icon) - - companion object { - fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): TextButtonViewHolder { - val view = - inflater.inflate( - R.layout.footer_actions_text_button, - /* root= */ root, - /* attachToRoot= */ false, - ) - root.addView(view) - return TextButtonViewHolder(view) - } - } -} - -private class NumberButtonViewHolder(val view: View) { - val number = view.requireViewById<TextView>(R.id.number) - val newDot = view.requireViewById<ImageView>(R.id.new_dot) - - companion object { - fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): NumberButtonViewHolder { - val view = - inflater.inflate( - R.layout.footer_actions_number_button, - /* root= */ root, - /* attachToRoot= */ false, - ) - root.addView(view) - return NumberButtonViewHolder(view) - } - } -} - -private class IconButtonViewHolder(val view: View) { - val icon = view.requireViewById<ImageView>(R.id.icon) - - companion object { - fun createAndAdd( - inflater: LayoutInflater, - root: ViewGroup, - isLast: Boolean, - ): IconButtonViewHolder { - val view = - inflater.inflate( - R.layout.footer_actions_icon_button, - /* root= */ root, - /* attachToRoot= */ false, - ) - - // All buttons have a background with an inset of qs_footer_action_inset, so the last - // button must have a negative inset of -qs_footer_action_inset to compensate and be - // aligned with its parent. - val marginEnd = - if (isLast) { - -view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset) - } else { - 0 - } - - val size = - view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_button_size) - root.addView( - view, - LinearLayout.LayoutParams(size, size).apply { this.marginEnd = marginEnd }, - ) - return IconButtonViewHolder(view) - } - } -} 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/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt index e581bfceb18f..095bdf2ff5bd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt @@ -19,38 +19,26 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -/** Repository for retrieving the list of [TileSpec] to be displayed as icons. */ +/** Repository for checking if a tile should be displayed as an icon. */ interface IconTilesRepository { - val iconTilesSpecs: StateFlow<Set<TileSpec>> + fun isIconTile(spec: TileSpec): Boolean } @SysUISingleton class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository { - private val _iconTilesSpecs = - MutableStateFlow( + override fun isIconTile(spec: TileSpec): Boolean { + return !LARGE_TILES.contains(spec) + } + + companion object { + private val LARGE_TILES = setOf( - TileSpec.create("airplane"), - TileSpec.create("battery"), - TileSpec.create("cameratoggle"), - TileSpec.create("cast"), - TileSpec.create("color_correction"), - TileSpec.create("inversion"), - TileSpec.create("saver"), + TileSpec.create("internet"), + TileSpec.create("bt"), TileSpec.create("dnd"), - TileSpec.create("flashlight"), - TileSpec.create("location"), - TileSpec.create("mictoggle"), - TileSpec.create("nfc"), - TileSpec.create("night"), - TileSpec.create("rotation") + TileSpec.create("cast"), ) - ) - - /** Set of toggleable tiles that are suitable for being shown as an icon. */ - override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow() + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt new file mode 100644 index 000000000000..f3e5b8f0c214 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import android.content.Context +import android.content.SharedPreferences +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserFileManager +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.kotlin.SharedPreferencesExt.observe +import com.android.systemui.util.kotlin.emitOnStart +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +/** Repository for QS user preferences. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class QSPreferencesRepository +@Inject +constructor( + private val userFileManager: UserFileManager, + private val userRepository: UserRepository, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + /** Whether to show the labels on icon tiles for the current user. */ + val showLabels: Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest { userInfo -> + val prefs = getSharedPrefs(userInfo.id) + prefs.observe().emitOnStart().map { prefs.getBoolean(ICON_LABELS_KEY, false) } + } + .flowOn(backgroundDispatcher) + + /** Sets for the current user whether to show the labels on icon tiles. */ + fun setShowLabels(showLabels: Boolean) { + with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) { + edit().putBoolean(ICON_LABELS_KEY, showLabels).apply() + } + } + + private fun getSharedPrefs(userId: Int): SharedPreferences { + return userFileManager.getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + userId, + ) + } + + companion object { + private const val ICON_LABELS_KEY = "show_icon_labels" + const val FILE_NAME = "quick_settings_prefs" + } +} 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..6a899b07b2a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.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.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 preferencesInteractor: QSPreferencesInteractor, + @IconLabelVisibilityLog private val logBuffer: LogBuffer, + @Application scope: CoroutineScope, +) { + val showLabels: StateFlow<Boolean> = + preferencesInteractor.showLabels + .onEach { logChange(it) } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + fun setShowLabels(showLabels: Boolean) { + preferencesInteractor.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/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt index ccc1c6e9135c..524ea8b70100 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt @@ -20,10 +20,9 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.data.repository.IconTilesRepository import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow /** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */ @SysUISingleton -class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) { - val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs +class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) { + fun isIconTile(spec: TileSpec): Boolean = repo.isIconTile(spec) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt index b437f645d4bc..e99c64c8c1f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt @@ -38,14 +38,13 @@ constructor( override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> { val newTiles: MutableList<TileSpec> = mutableListOf() val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value) - val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value val tilesQueue = ArrayDeque( tiles.map { SizedTile( it, width = - if (iconTilesSet.contains(it)) { + if (iconTilesInteractor.isIconTile(it)) { 1 } else { 2 diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt new file mode 100644 index 000000000000..811be80d23fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.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.dagger.SysUISingleton +import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class QSPreferencesInteractor @Inject constructor(private val repo: QSPreferencesRepository) { + val showLabels: Flow<Boolean> = repo.showLabels + + fun setShowLabels(showLabels: Boolean) { + repo.setShowLabels(showLabels) + } +} 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..2f0fe221a2b4 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,15 +52,13 @@ 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 columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { items( tiles.size, span = { index -> - val iconOnly = iconTilesSpecs.contains(tiles[index].spec) - if (iconOnly) { + if (iconTilesViewModel.isIconTile(tiles[index].spec)) { GridItemSpan(1) } else { GridItemSpan(2) @@ -68,9 +66,9 @@ constructor( } ) { index -> Tile( - tiles[index], - iconTilesSpecs.contains(tiles[index].spec), - Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) + tile = tiles[index], + iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec), + modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) ) } } @@ -83,12 +81,11 @@ constructor( onAddTile: (TileSpec, Int) -> Unit, onRemoveTile: (TileSpec) -> Unit, ) { - val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() - val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() + val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() DefaultEditTileGrid( tiles = tiles, - iconOnlySpecs = iconOnlySpecs, + isIconOnly = iconTilesViewModel::isIconTile, columns = GridCells.Fixed(columns), modifier = modifier, onAddTile = onAddTile, 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..d60076745a78 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,21 +33,22 @@ 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 -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 +56,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,10 +65,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 (smallTiles, largeTiles) = tiles.partition { iconTilesSpecs.contains(it.spec) } + val columns by viewModel.columns.collectAsStateWithLifecycle() + val showLabels by viewModel.showLabels.collectAsStateWithLifecycle() + val largeTileHeight = tileHeight() + val iconTileHeight = tileHeight(showLabels) + val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) } TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { // Large tiles @@ -78,7 +77,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 +87,8 @@ constructor( Tile( tile = smallTiles[index], iconOnly = true, - modifier = Modifier.height(tileHeight) + showLabels = showLabels, + modifier = Modifier.height(iconTileHeight) ) } } @@ -101,36 +101,63 @@ constructor( onAddTile: (TileSpec, Int) -> Unit, onRemoveTile: (TileSpec) -> Unit ) { - val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() - val columns by gridSizeInteractor.columns.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 { onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) } - 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, + isIconOnly = viewModel::isIconTile, columns = columns, + showLabels = showLabels, ) AvailableTiles( tiles = otherTiles, - tileHeight = tileHeight, + largeTileHeight = largeTileHeight, + iconTileHeight = iconTileHeight, tilePadding = tilePadding, addTileToEnd = addTileToEnd, - isIconOnly = isIconOnly, + isIconOnly = viewModel::isIconTile, + showLabels = showLabels, columns = columns, ) } @@ -139,23 +166,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 +198,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 +213,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, iconTileHeight, columns, tilePadding) // Add up the height of all three grids and add padding in between val gridHeight = @@ -199,11 +243,23 @@ 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 - editTiles(tilesCustom, ClickAction.ADD, addTileToEnd, isIconOnly) + // Custom tiles, all icons + editTiles( + tilesCustom, + ClickAction.ADD, + addTileToEnd, + isIconOnly, + showLabels = showLabels + ) } } } 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..7f4e0a7047b8 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,14 +60,13 @@ constructor( // Icon [3 | 4] // Large [6 | 8] val columns = 12 - val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() val stretchedTiles = remember(tiles) { val sizedTiles = tiles.map { SizedTile( it, - if (iconTilesSpecs.contains(it.spec)) { + if (iconTilesViewModel.isIconTile(it.spec)) { 3 } else { 6 @@ -80,9 +79,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 = iconTilesViewModel.isIconTile(stretchedTiles[index].tile.spec), + modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) ) } } @@ -95,12 +94,11 @@ constructor( onAddTile: (TileSpec, Int) -> Unit, onRemoveTile: (TileSpec) -> Unit ) { - val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() - val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() + val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() DefaultEditTileGrid( tiles = tiles, - iconOnlySpecs = iconOnlySpecs, + isIconOnly = iconTilesViewModel::isIconTile, columns = GridCells.Fixed(columns), modifier = modifier, onAddTile = onAddTile, 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..f776bf08c9e4 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, ) } } @@ -160,7 +165,7 @@ fun TileLazyGrid( @Composable fun DefaultEditTileGrid( tiles: List<EditTileViewModel>, - iconOnlySpecs: Set<TileSpec>, + isIconOnly: (TileSpec) -> Boolean, columns: GridCells, modifier: Modifier, onAddTile: (TileSpec, Int) -> Unit, @@ -171,8 +176,6 @@ fun DefaultEditTileGrid( val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) } - val isIconOnly: (TileSpec) -> Boolean = - remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } TileLazyGrid(modifier = modifier, columns = columns) { // These Text are just placeholders to see the different sections. Not final UI. @@ -213,6 +216,7 @@ fun LazyGridScope.editTiles( clickAction: ClickAction, onClick: (TileSpec) -> Unit, isIconOnly: (TileSpec) -> Boolean, + showLabels: Boolean = false, indicatePosition: Boolean = false, ) { items( @@ -250,10 +254,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 +288,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 +305,7 @@ fun EditTile( colors = colors, icon = tileViewModel.icon, iconOnly = iconOnly, + showLabels = showLabels, animateIconToEnd = true, ) } @@ -380,9 +389,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 +427,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..12cbde2cbc91 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.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..117c85c9c3ba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.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.IconTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject + +interface IconTilesViewModel { + fun isIconTile(spec: TileSpec): Boolean +} + +@SysUISingleton +class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) : + IconTilesViewModel { + override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec) +} 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/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt index 214e9f097642..24b80b8b069a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt @@ -158,6 +158,9 @@ constructor( override suspend fun prependDefault( userId: Int, ) { + if (retailModeRepository.inRetailMode) { + return + } userTileRepositories.get(userId)?.prependDefault() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt index 8ad5cb2c0a34..aca8733bff7b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt @@ -60,15 +60,21 @@ constructor( _tiles = changeEvents .scan(loadTilesFromSettingsAndParse(userId)) { current, change -> - change.apply(current).also { - if (current != it) { - if (change is RestoreTiles) { - logger.logTilesRestoredAndReconciled(current, it, userId) - } else { - logger.logProcessTileChange(change, it, userId) + change + .apply(current) + .also { + if (current != it) { + if (change is RestoreTiles) { + logger.logTilesRestoredAndReconciled(current, it, userId) + } else { + logger.logProcessTileChange(change, it, userId) + } } } - } + // Distinct preserves the order of the elements removing later + // duplicates, + // all tiles should be different + .distinct() } .flowOn(backgroundDispatcher) .stateIn(applicationScope) diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt index 08e39204386e..a0c9737de0ee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt @@ -22,6 +22,7 @@ import com.android.systemui.qs.pipeline.domain.model.AutoAddable import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.HearingDevicesTile import com.android.systemui.qs.tiles.OneHandedModeTile import com.android.systemui.qs.tiles.ReduceBrightColorsTile @@ -50,6 +51,10 @@ object A11yShortcutAutoAddableList { TileSpec.create(ReduceBrightColorsTile.TILE_SPEC), AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME ), + factory.create( + TileSpec.create(HearingDevicesTile.TILE_SPEC), + AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME + ) ) } else { emptySet() diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt index b7fcef4376ea..97b5e87d7167 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -43,6 +43,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.qs.tiles.di.NewQSTileFactory import com.android.systemui.qs.toProto +import com.android.systemui.retail.data.repository.RetailModeRepository import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise @@ -137,6 +138,7 @@ constructor( private val installedTilesComponentRepository: InstalledTilesComponentRepository, private val userRepository: UserRepository, private val minimumTilesRepository: MinimumTilesRepository, + private val retailModeRepository: RetailModeRepository, private val customTileStatePersister: CustomTileStatePersister, private val newQSTileFactory: Lazy<NewQSTileFactory>, private val tileFactory: QSFactory, @@ -178,6 +180,14 @@ constructor( installedTilesComponentRepository.getInstalledTilesComponents(it) } + private val minTiles: Int + get() = + if (retailModeRepository.inRetailMode) { + 1 + } else { + minimumTilesRepository.minNumberOfTiles + } + init { if (featureFlags.pipelineEnabled) { startTileCollection() @@ -273,7 +283,7 @@ constructor( newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys, newUser ) - if (newResolvedTiles.size < minimumTilesRepository.minNumberOfTiles) { + if (newResolvedTiles.size < minTiles) { // We ended up with not enough tiles (some may be not installed). // Prepend the default set of tiles launch { tileSpecRepository.prependDefault(currentUser.value) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 56588ff75a5a..8887f5857baf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -677,6 +677,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mId = id; } + public int getResourceId() { + return mId; + } + @Override public boolean equals(Object o) { return o instanceof DrawableIconWithRes && ((DrawableIconWithRes) o).mId == mId; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index c6dfdd5c137b..1143c304f594 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -332,6 +332,21 @@ open class QSTileViewImpl @JvmOverloads constructor( override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { super.onLayout(changed, l, t, r, b) updateHeight() + maybeUpdateLongPressEffectDimensions() + } + + private fun maybeUpdateLongPressEffectDimensions() { + if (!isLongClickable || longPressEffect == null) return + + val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) { + heightOverride + } else { + measuredHeight + } + initialLongPressProperties?.height = actualHeight.toFloat() + initialLongPressProperties?.width = measuredWidth.toFloat() + finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * actualHeight + finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * measuredWidth } override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 76aa146d0531..f218d86a5aa1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -16,8 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.graphics.drawable.Icon.TYPE_URI; import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; +import static android.graphics.drawable.Icon.TYPE_URI; +import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; +import static android.graphics.drawable.Icon.TYPE_RESOURCE; +import static android.graphics.drawable.Icon.TYPE_BITMAP; +import static android.graphics.drawable.Icon.TYPE_ADAPTIVE_BITMAP; +import static android.graphics.drawable.Icon.TYPE_DATA; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE; @@ -237,11 +242,21 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { return; } mSelectedCard = cards.get(selectedIndex); - android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage(); - if (cardImageIcon.getType() == TYPE_URI) { - mCardViewDrawable = null; - } else { - mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + switch (mSelectedCard.getCardImage().getType()) { + case TYPE_URI: + case TYPE_URI_ADAPTIVE_BITMAP: + mCardViewDrawable = null; + break; + case TYPE_RESOURCE: + case TYPE_BITMAP: + case TYPE_ADAPTIVE_BITMAP: + case TYPE_DATA: + mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + break; + default: + Log.e(TAG, "Unknown icon type: " + mSelectedCard.getCardImage().getType()); + mCardViewDrawable = null; + break; } refreshState(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index b057476ca194..c091ac3dea50 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -118,6 +118,8 @@ import javax.inject.Inject; public class InternetDialogController implements AccessPointController.AccessPointCallback { private static final String TAG = "InternetDialogController"; + private static final String ACTION_NETWORK_PROVIDER_SETTINGS = + "android.settings.NETWORK_PROVIDER_SETTINGS"; private static final String ACTION_WIFI_SCANNING_SETTINGS = "android.settings.WIFI_SCANNING_SETTINGS"; /** @@ -361,8 +363,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi @VisibleForTesting protected Intent getSettingsIntent() { - return new Intent(Settings.ACTION_NETWORK_PROVIDER_SETTINGS).addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK); + return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } @Nullable @@ -935,6 +936,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi mHasActiveSubIdOnDds = false; Log.e(TAG, "Can't get DDS subscriptionInfo"); return; + } else if (ddsSubInfo.isOnlyNonTerrestrialNetwork()) { + mHasActiveSubIdOnDds = false; + Log.d(TAG, "This is NTN, so do not show mobile data"); + return; } mHasActiveSubIdOnDds = isEmbeddedSubscriptionVisible(ddsSubInfo); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt index c0fc52e85866..f0889433094a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.impl.alarm.domain import android.content.res.Resources import android.content.res.Resources.Theme +import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel @@ -82,7 +83,8 @@ constructor( secondaryLabel = resources.getString(R.string.qs_alarm_tile_no_alarm) } } - + iconRes = R.drawable.ic_alarm + icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } sideViewIcon = QSTileState.SideViewIcon.Chevron contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt index 0c08fbacfcfc..bcf0935adf85 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt @@ -38,17 +38,10 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.battery_detail_switch_title) contentDescription = label - - icon = { - Icon.Loaded( - resources.getDrawable( - if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on - else R.drawable.qs_battery_saver_icon_off, - theme - ), - null - ) - } + iconRes = + if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on + else R.drawable.qs_battery_saver_icon_off + icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } sideViewIcon = QSTileState.SideViewIcon.None diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt index 1efbfd70fa98..cad7c65ad112 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt @@ -37,6 +37,8 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction) + iconRes = R.drawable.ic_qs_color_correction + if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = subtitleArray[2] diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt index 58e761370457..d7d61241fc6c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt @@ -37,14 +37,16 @@ constructor( override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { + iconRes = + if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) { + R.drawable.qs_flashlight_icon_on + } else { + R.drawable.qs_flashlight_icon_off + } val icon = Icon.Loaded( resources.getDrawable( - if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) { - R.drawable.qs_flashlight_icon_on - } else { - R.drawable.qs_flashlight_icon_off - }, + iconRes!!, theme, ), contentDescription = null diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt index 26069c774364..6b4dda13a5e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt @@ -36,10 +36,11 @@ constructor( override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { + iconRes = R.drawable.ic_qs_font_scaling val icon = Icon.Loaded( resources.getDrawable( - R.drawable.ic_qs_font_scaling, + iconRes!!, theme, ), contentDescription = null diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt index caae4d26634d..e543e4bdc930 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt @@ -53,6 +53,7 @@ constructor( stateDescription = data.stateDescription.loadContentDescription(context) contentDescription = data.contentDescription.loadContentDescription(context) + iconRes = data.iconId if (data.icon != null) { this.icon = { data.icon } } else if (data.iconId != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt index 4af985424a39..40aee65f41a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt @@ -41,22 +41,13 @@ constructor( if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = subtitleArray[2] - icon = { - Icon.Loaded( - resources.getDrawable(R.drawable.qs_invert_colors_icon_on, theme), - null - ) - } + iconRes = R.drawable.qs_invert_colors_icon_on } else { activationState = QSTileState.ActivationState.INACTIVE secondaryLabel = subtitleArray[1] - icon = { - Icon.Loaded( - resources.getDrawable(R.drawable.qs_invert_colors_icon_off, theme), - null - ) - } + iconRes = R.drawable.qs_invert_colors_icon_off } + icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt index fe5445d00670..d58f5abcd018 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt @@ -37,14 +37,16 @@ constructor( override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { + iconRes = + if (data.isEnabled) { + R.drawable.qs_location_icon_on + } else { + R.drawable.qs_location_icon_off + } val icon = Icon.Loaded( resources.getDrawable( - if (data.isEnabled) { - R.drawable.qs_location_icon_on - } else { - R.drawable.qs_location_icon_off - }, + iconRes!!, theme, ), contentDescription = null diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt index 5c2dcfcaf37c..bcf7cc763b9e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt @@ -52,21 +52,14 @@ constructor( if (data.isActivated) { activationState = QSTileState.ActivationState.ACTIVE - val loadedIcon = - Icon.Loaded( - resources.getDrawable(R.drawable.qs_nightlight_icon_on, theme), - contentDescription = null - ) - icon = { loadedIcon } + iconRes = R.drawable.qs_nightlight_icon_on } else { activationState = QSTileState.ActivationState.INACTIVE - val loadedIcon = - Icon.Loaded( - resources.getDrawable(R.drawable.qs_nightlight_icon_off, theme), - contentDescription = null - ) - icon = { loadedIcon } + iconRes = R.drawable.qs_nightlight_icon_off } + val loadedIcon = + Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) + icon = { loadedIcon } secondaryLabel = getSecondaryLabel(data, resources) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt index 9166ed8faacf..40809960735f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt @@ -38,15 +38,8 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded) label = resources.getString(R.string.quick_settings_onehanded_label) - icon = { - Icon.Loaded( - resources.getDrawable( - com.android.internal.R.drawable.ic_qs_one_handed_mode, - theme - ), - null - ) - } + iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode + icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE secondaryLabel = subtitleArray[2] diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt index 45a77179fb3f..823174234b13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt @@ -38,9 +38,8 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { label = resources.getString(R.string.qr_code_scanner_title) contentDescription = label - icon = { - Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null) - } + iconRes = R.drawable.ic_qr_code_scanner + icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } sideViewIcon = QSTileState.SideViewIcon.Chevron supportedActions = setOf(QSTileState.UserAction.CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt index fca93dfe47da..85ee02207ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt @@ -39,28 +39,23 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { if (data.isEnabled) { activationState = QSTileState.ActivationState.ACTIVE - icon = { - Icon.Loaded( - drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_on, theme), - contentDescription = null - ) - } - + iconRes = R.drawable.qs_extra_dim_icon_on secondaryLabel = resources .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_ACTIVE] } else { activationState = QSTileState.ActivationState.INACTIVE - icon = { - Icon.Loaded( - drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_off, theme), - contentDescription = null - ) - } + iconRes = R.drawable.qs_extra_dim_icon_off secondaryLabel = resources .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE] } + icon = { + Icon.Loaded( + drawable = resources.getDrawable(iconRes!!, theme), + contentDescription = null + ) + } label = resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name) contentDescription = label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt index 070cdef336e5..8e80fb0b4c3e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt @@ -44,12 +44,7 @@ constructor( if (data.isRotationLocked) { activationState = QSTileState.ActivationState.INACTIVE this.secondaryLabel = EMPTY_SECONDARY_STRING - this.icon = { - Icon.Loaded( - resources.getDrawable(R.drawable.qs_auto_rotate_icon_off, theme), - contentDescription = null - ) - } + iconRes = R.drawable.qs_auto_rotate_icon_off } else { activationState = QSTileState.ActivationState.ACTIVE this.secondaryLabel = @@ -58,12 +53,10 @@ constructor( } else { EMPTY_SECONDARY_STRING } - this.icon = { - Icon.Loaded( - resources.getDrawable(R.drawable.qs_auto_rotate_icon_on, theme), - contentDescription = null - ) - } + this.iconRes = R.drawable.qs_auto_rotate_icon_on + } + this.icon = { + Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) } if (isDeviceFoldable()) { this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt index df25600228a5..888bba87a03a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt @@ -36,7 +36,6 @@ constructor( override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { with(data) { - val iconRes: Int if (isEnabled) { activationState = QSTileState.ActivationState.ACTIVE iconRes = R.drawable.qs_data_saver_icon_on @@ -47,7 +46,7 @@ constructor( secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1] } val loadedIcon = - Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null) + Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) icon = { loadedIcon } contentDescription = label supportedActions = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt index b58774b8a17d..7446708cfebc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt @@ -42,9 +42,10 @@ constructor( when (data) { is ScreenRecordModel.Recording -> { activationState = QSTileState.ActivationState.ACTIVE + iconRes = R.drawable.qs_screen_record_icon_on val loadedIcon = Icon.Loaded( - resources.getDrawable(R.drawable.qs_screen_record_icon_on, theme), + resources.getDrawable(iconRes!!, theme), contentDescription = null ) icon = { loadedIcon } @@ -53,9 +54,10 @@ constructor( } is ScreenRecordModel.Starting -> { activationState = QSTileState.ActivationState.ACTIVE + iconRes = R.drawable.qs_screen_record_icon_on val loadedIcon = Icon.Loaded( - resources.getDrawable(R.drawable.qs_screen_record_icon_on, theme), + resources.getDrawable(iconRes!!, theme), contentDescription = null ) icon = { loadedIcon } @@ -65,9 +67,10 @@ constructor( } is ScreenRecordModel.DoingNothing -> { activationState = QSTileState.ActivationState.INACTIVE + iconRes = R.drawable.qs_screen_record_icon_off val loadedIcon = Icon.Loaded( - resources.getDrawable(R.drawable.qs_screen_record_icon_off, theme), + resources.getDrawable(iconRes!!, theme), contentDescription = null ) icon = { loadedIcon } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt index 52622d26348d..597cf274dcff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt @@ -50,15 +50,8 @@ constructor( contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) - icon = { - Icon.Loaded( - resources.getDrawable( - sensorPrivacyTileResources.getIconRes(data.isBlocked), - theme - ), - null - ) - } + iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked) + icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) } sideViewIcon = QSTileState.SideViewIcon.None diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt index ffef2b6ecfb5..f29c745d8119 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt @@ -117,12 +117,12 @@ constructor( } } - val iconRes = + iconRes = if (activationState == QSTileState.ActivationState.ACTIVE) R.drawable.qs_light_dark_theme_icon_on else R.drawable.qs_light_dark_theme_icon_off val loadedIcon = - Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null) + Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) icon = { loadedIcon } supportedActions = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt index 55445bb922a5..eee95b7311d3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt @@ -41,15 +41,9 @@ constructor( QSTileState.build(resources, theme, config.uiConfig) { label = getTileLabel()!! contentDescription = label - + iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status icon = { - Icon.Loaded( - resources.getDrawable( - com.android.internal.R.drawable.stat_sys_managed_profile_status, - theme - ), - contentDescription = null - ) + Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) } when (data) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index b927e41bc97e..ae6c0143f603 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -29,10 +29,14 @@ import kotlin.reflect.KClass * [QSTileState.build] for better state creation experience and preset default values for certain * fields. * + * @param iconRes For when we want to have Loaded icon, but still keep a reference to the resource + * id. A use case would be for tests that have to compare animated drawables. + * * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition */ data class QSTileState( val icon: () -> Icon?, + val iconRes: Int?, val label: CharSequence, val activationState: ActivationState, val secondaryLabel: CharSequence?, @@ -111,6 +115,7 @@ data class QSTileState( var icon: () -> Icon?, var label: CharSequence, ) { + var iconRes: Int? = null var activationState: ActivationState = ActivationState.INACTIVE var secondaryLabel: CharSequence? = null var supportedActions: Set<UserAction> = setOf(UserAction.CLICK) @@ -123,6 +128,7 @@ data class QSTileState( fun build(): QSTileState = QSTileState( icon, + iconRes, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index 5346b237111f..7be13e08f972 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -28,6 +28,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.QSHost import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon +import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -241,7 +242,9 @@ constructor( iconSupplier = Supplier { when (val stateIcon = viewModelState.icon()) { - is Icon.Loaded -> DrawableIcon(stateIcon.drawable) + is Icon.Loaded -> + if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable) + else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes) is Icon.Resource -> ResourceIcon.get(stateIcon.res) null -> null } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index fb872d538e6c..c7326b08e315 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -46,7 +46,6 @@ import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -89,8 +88,10 @@ interface QSSceneAdapter { /** * A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by * the interactor. + * + * A null value means that there is no inflated view yet. See [inflate]. */ - val qsView: Flow<View> + val qsView: StateFlow<View?> /** Sets the [MirrorController] in [QSImpl]. Set to `null` to remove. */ fun setBrightnessMirrorController(mirrorController: MirrorController?) @@ -101,9 +102,22 @@ interface QSSceneAdapter { */ suspend fun inflate(context: Context) - /** Set the current state for QS. [state]. */ + /** + * Set the current state for QS. [state]. + * + * This will not trigger expansion (animation between QQS or QS) or squishiness to be applied. + * For that, use [applyLatestExpansionAndSquishiness] outside of the composition phase. + */ fun setState(state: State) + /** + * Explicitly applies the expansion and squishiness value from the latest state set. Call this + * only outside of the composition phase as this will call [QSImpl.setQsExpansion] that is + * normally called during animations. In particular, this will read the value of + * [State.squishiness], that is not safe to read in the composition phase. + */ + fun applyLatestExpansionAndSquishiness() + /** Propagates the bottom nav bar size to [QSImpl] to be used as necessary. */ suspend fun applyBottomNavBarPadding(padding: Int) @@ -141,14 +155,24 @@ interface QSSceneAdapter { override val squishiness = { 1f } } - /** State for appearing QQS from Lockscreen or Gone */ - data class UnsquishingQQS(override val squishiness: () -> Float) : State { + /** + * State for appearing QQS from Lockscreen or Gone. + * + * This should not be a data class, as it has a method parameter and even if it's the same + * lambda the output value may have changed. + */ + class UnsquishingQQS(override val squishiness: () -> Float) : State { override val isVisible = true override val expansion = 0f } - /** State for appearing QS from Lockscreen or Gone, used in Split shade */ - data class UnsquishingQS(override val squishiness: () -> Float) : State { + /** + * State for appearing QS from Lockscreen or Gone, used in Split shade. + * + * This should not be a data class, as it has a method parameter and even if it's the same + * lambda the output value may have changed. + */ + class UnsquishingQS(override val squishiness: () -> Float) : State { override val isVisible = true override val expansion = 1f } @@ -236,7 +260,10 @@ constructor( private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null) val qsImpl = _qsImpl.asStateFlow() - override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull() + override val qsView: StateFlow<View?> = + _qsImpl + .map { it?.view } + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), _qsImpl.value?.view) override val qqsHeight: Int get() = qsImpl.value?.qqsHeight ?: 0 @@ -370,7 +397,12 @@ constructor( setQsVisible(state.isVisible) setExpanded(state.isVisible && state.expansion > 0f) setListening(state.isVisible) - setQsExpansion(state.expansion, 1f, 0f, state.squishiness()) + } + + override fun applyLatestExpansionAndSquishiness() { + val qsImpl = _qsImpl.value + val state = state.value + qsImpl?.setQsExpansion(state.expansion, 1f, 0f, state.squishiness()) } override fun dump(pw: PrintWriter, args: Array<out String>) { diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 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/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index 5469a4e9da08..e024710ed3eb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -119,6 +119,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback { IMediaProjection projection = IMediaProjection.Stub.asInterface(proj.asBinder()); if (mCaptureRegion != null) { projection.setLaunchCookie(mCaptureRegion.getLaunchCookie()); + projection.setTaskId(mCaptureRegion.getTaskId()); } mMediaProjection = new MediaProjection(mContext, projection); mMediaProjection.registerCallback(this, mHandler); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 4ab09185fcdd..9e622801a01b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -90,12 +90,18 @@ constructor( ) } systemUiProxy.dismissKeyguard() - transitionCoordinator?.startExit() + var transitionOptions: ActivityOptions? = null + if (transitionCoordinator?.decor?.isAttachedToWindow == true) { + transitionCoordinator.startExit() + transitionOptions = options + } if (user == myUserHandle()) { - withContext(mainDispatcher) { context.startActivity(intent, options?.toBundle()) } + withContext(mainDispatcher) { + context.startActivity(intent, transitionOptions?.toBundle()) + } } else { - launchCrossProfileIntent(user, intent, options?.toBundle()) + launchCrossProfileIntent(user, intent, transitionOptions?.toBundle()) } if (overrideTransition) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt new file mode 100644 index 000000000000..2ffb7835f400 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.app.assist.AssistContent +import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance +import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.UUID + +/** + * Responsible for obtaining the actions for each screenshot and sending them to the view model. + * Ensures that only actions from screenshots that are currently being shown are added to the view + * model. + */ +class ScreenshotActionsController +@AssistedInject +constructor( + private val viewModel: ScreenshotViewModel, + private val actionsProviderFactory: ScreenshotActionsProvider.Factory, + @Assisted val actionExecutor: ActionExecutor +) { + private val actionProviders: MutableMap<UUID, ScreenshotActionsProvider> = mutableMapOf() + private var currentScreenshotId: UUID? = null + + fun setCurrentScreenshot(screenshot: ScreenshotData): UUID { + val screenshotId = UUID.randomUUID() + currentScreenshotId = screenshotId + actionProviders[screenshotId] = + actionsProviderFactory.create( + screenshotId, + screenshot, + actionExecutor, + ActionsCallback(screenshotId), + ) + return screenshotId + } + + fun endScreenshotSession() { + currentScreenshotId = null + } + + fun onAssistContent(screenshotId: UUID, assistContent: AssistContent?) { + actionProviders[screenshotId]?.onAssistContent(assistContent) + } + + fun onScrollChipReady(screenshotId: UUID, onClick: Runnable) { + if (screenshotId == currentScreenshotId) { + actionProviders[screenshotId]?.onScrollChipReady(onClick) + } + } + + fun onScrollChipInvalidated() { + for (provider in actionProviders.values) { + provider.onScrollChipInvalidated() + } + } + + fun setCompletedScreenshot(screenshotId: UUID, result: ScreenshotSavedResult) { + if (screenshotId == currentScreenshotId) { + actionProviders[screenshotId]?.setCompletedScreenshot(result) + } + } + + @AssistedFactory + interface Factory { + fun getController(actionExecutor: ActionExecutor): ScreenshotActionsController + } + + inner class ActionsCallback(private val screenshotId: UUID) { + fun providePreviewAction(onClick: () -> Unit) { + if (screenshotId == currentScreenshotId) { + viewModel.setPreviewAction(onClick) + } + } + + fun provideActionButton( + appearance: ActionButtonAppearance, + showDuringEntrance: Boolean, + onClick: () -> Unit + ): Int { + if (screenshotId == currentScreenshotId) { + return viewModel.addAction(appearance, showDuringEntrance, onClick) + } + return 0 + } + + fun updateActionButtonAppearance(buttonId: Int, appearance: ActionButtonAppearance) { + if (screenshotId == currentScreenshotId) { + viewModel.updateActionAppearance(buttonId, appearance) + } + } + + fun updateActionButtonVisibility(buttonId: Int, visible: Boolean) { + if (screenshotId == currentScreenshotId) { + viewModel.setActionVisibility(buttonId, visible) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index a1dd4157d996..b8029c8b1cc3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -29,10 +29,10 @@ import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance -import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import java.util.UUID /** * Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI @@ -51,9 +51,10 @@ interface ScreenshotActionsProvider { interface Factory { fun create( + requestId: UUID, request: ScreenshotData, - requestId: String, actionExecutor: ActionExecutor, + actionsCallback: ScreenshotActionsController.ActionsCallback, ): ScreenshotActionsProvider } } @@ -62,11 +63,11 @@ class DefaultScreenshotActionsProvider @AssistedInject constructor( private val context: Context, - private val viewModel: ScreenshotViewModel, private val uiEventLogger: UiEventLogger, + @Assisted val requestId: UUID, @Assisted val request: ScreenshotData, - @Assisted val requestId: String, @Assisted val actionExecutor: ActionExecutor, + @Assisted val actionsCallback: ScreenshotActionsController.ActionsCallback, ) : ScreenshotActionsProvider { private var addedScrollChip = false private var onScrollClick: Runnable? = null @@ -74,7 +75,7 @@ constructor( private var result: ScreenshotSavedResult? = null init { - viewModel.setPreviewAction { + actionsCallback.providePreviewAction { debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" } uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> @@ -85,40 +86,41 @@ constructor( ) } } - viewModel.addAction( + + actionsCallback.provideActionButton( ActionButtonAppearance( - AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit), - context.resources.getString(R.string.screenshot_edit_label), - context.resources.getString(R.string.screenshot_edit_description), + AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share), + context.resources.getString(R.string.screenshot_share_label), + context.resources.getString(R.string.screenshot_share_description), ), showDuringEntrance = true, ) { - debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" } - uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString) + debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" } + uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> actionExecutor.startSharedTransition( - createEdit(result.uri, context), + createShareWithSubject(result.uri, result.subject), result.user, - true + false ) } } - viewModel.addAction( + actionsCallback.provideActionButton( ActionButtonAppearance( - AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share), - context.resources.getString(R.string.screenshot_share_label), - context.resources.getString(R.string.screenshot_share_description), + AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit), + context.resources.getString(R.string.screenshot_edit_label), + context.resources.getString(R.string.screenshot_edit_description), ), showDuringEntrance = true, ) { - debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" } - uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString) + debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" } + uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> actionExecutor.startSharedTransition( - createShareWithSubject(result.uri, result.subject), + createEdit(result.uri, context), result.user, - false + true ) } } @@ -127,7 +129,7 @@ constructor( override fun onScrollChipReady(onClick: Runnable) { onScrollClick = onClick if (!addedScrollChip) { - viewModel.addAction( + actionsCallback.provideActionButton( ActionButtonAppearance( AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll), context.resources.getString(R.string.screenshot_scroll_label), @@ -161,9 +163,10 @@ constructor( @AssistedFactory interface Factory : ScreenshotActionsProvider.Factory { override fun create( + requestId: UUID, request: ScreenshotData, - requestId: String, actionExecutor: ActionExecutor, + actionsCallback: ScreenshotActionsController.ActionsCallback, ): DefaultScreenshotActionsProvider } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 4c60090743fd..e8dfac868546 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -192,7 +192,6 @@ public class ScreenshotController { private final WindowContext mContext; private final FeatureFlags mFlags; private final ScreenshotViewProxy mViewProxy; - private final ScreenshotActionsProvider.Factory mActionsProviderFactory; private final ScreenshotNotificationsController mNotificationsController; private final ScreenshotSmartActions mScreenshotSmartActions; private final UiEventLogger mUiEventLogger; @@ -202,7 +201,7 @@ public class ScreenshotController { private final ExecutorService mBgExecutor; private final BroadcastSender mBroadcastSender; private final BroadcastDispatcher mBroadcastDispatcher; - private final ActionExecutor mActionExecutor; + private final ScreenshotActionsController mActionsController; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; @@ -217,16 +216,18 @@ public class ScreenshotController { private final ActionIntentExecutor mActionIntentExecutor; private final UserManager mUserManager; private final AssistContentRequester mAssistContentRequester; + private final ActionExecutor mActionExecutor; + private final MessageContainerController mMessageContainerController; private final AnnouncementResolver mAnnouncementResolver; private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; private boolean mScreenshotTakenInPortrait; - private boolean mBlockAttach; + private boolean mAttachRequested; + private boolean mDetachRequested; private Animator mScreenshotAnimation; private RequestCallback mCurrentRequestCallback; - private ScreenshotActionsProvider mActionsProvider; private String mPackageName = ""; private final BroadcastReceiver mCopyBroadcastReceiver; @@ -253,7 +254,6 @@ public class ScreenshotController { WindowManager windowManager, FeatureFlags flags, ScreenshotViewProxy.Factory viewProxyFactory, - ScreenshotActionsProvider.Factory actionsProviderFactory, ScreenshotSmartActions screenshotSmartActions, ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory, UiEventLogger uiEventLogger, @@ -265,6 +265,7 @@ public class ScreenshotController { BroadcastSender broadcastSender, BroadcastDispatcher broadcastDispatcher, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, + ScreenshotActionsController.Factory screenshotActionsControllerFactory, ActionIntentExecutor actionIntentExecutor, ActionExecutor.Factory actionExecutorFactory, UserManager userManager, @@ -276,7 +277,6 @@ public class ScreenshotController { @Assisted boolean showUIOnExternalDisplay ) { mScreenshotSmartActions = screenshotSmartActions; - mActionsProviderFactory = actionsProviderFactory; mNotificationsController = screenshotNotificationsControllerFactory.create( display.getDisplayId()); mUiEventLogger = uiEventLogger; @@ -327,6 +327,8 @@ public class ScreenshotController { finishDismiss(); return Unit.INSTANCE; }); + mActionsController = screenshotActionsControllerFactory.getController(mActionExecutor); + // Sound is only reproduced from the controller of the default display. if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) { @@ -403,20 +405,21 @@ public class ScreenshotController { return; } + final UUID requestId; if (screenshotShelfUi2()) { - final UUID requestId = UUID.randomUUID(); - final String screenshotId = String.format("Screenshot_%s", requestId); - mActionsProvider = mActionsProviderFactory.create( - screenshot, screenshotId, mActionExecutor); + requestId = mActionsController.setCurrentScreenshot(screenshot); saveScreenshotInBackground(screenshot, requestId, finisher); if (screenshot.getTaskId() >= 0) { - mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), - assistContent -> mActionsProvider.onAssistContent(assistContent)); + mAssistContentRequester.requestAssistContent( + screenshot.getTaskId(), + assistContent -> + mActionsController.onAssistContent(requestId, assistContent)); } else { - mActionsProvider.onAssistContent(null); + mActionsController.onAssistContent(requestId, null); } } else { + requestId = UUID.randomUUID(); // passed through but unused for legacy UI saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); } @@ -425,7 +428,7 @@ public class ScreenshotController { setWindowFocusable(true); mViewProxy.requestFocus(); - enqueueScrollCaptureRequest(screenshot.getUserHandle()); + enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); attachWindow(); @@ -586,11 +589,11 @@ public class ScreenshotController { mWindow.setContentView(mViewProxy.getView()); } - private void enqueueScrollCaptureRequest(UserHandle owner) { + private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) { // Wait until this window is attached to request because it is // the reference used to locate the target window (below). withWindowAttached(() -> { - requestScrollCapture(owner); + requestScrollCapture(requestId, owner); mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback( new ViewRootImpl.ActivityConfigCallback() { @Override @@ -600,14 +603,14 @@ public class ScreenshotController { // Hide the scroll chip until we know it's available in this // orientation if (screenshotShelfUi2()) { - mActionsProvider.onScrollChipInvalidated(); + mActionsController.onScrollChipInvalidated(); } else { mViewProxy.hideScrollChip(); } // Delay scroll capture eval a bit to allow the underlying activity // to set up in the new orientation. mScreenshotHandler.postDelayed( - () -> requestScrollCapture(owner), 150); + () -> requestScrollCapture(requestId, owner), 150); mViewProxy.updateInsets( mWindowManager.getCurrentWindowMetrics().getWindowInsets()); // Screenshot animation calculations won't be valid anymore, @@ -629,7 +632,7 @@ public class ScreenshotController { }); } - private void requestScrollCapture(UserHandle owner) { + private void requestScrollCapture(UUID requestId, UserHandle owner) { mScrollCaptureExecutor.requestScrollCapture( mDisplay.getDisplayId(), mWindow.getDecorView().getWindowToken(), @@ -637,10 +640,8 @@ public class ScreenshotController { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, response.getPackageName()); if (screenshotShelfUi2()) { - if (mActionsProvider != null) { - mActionsProvider.onScrollChipReady( - () -> onScrollButtonClicked(owner, response)); - } + mActionsController.onScrollChipReady(requestId, + () -> onScrollButtonClicked(owner, response)); } else { mViewProxy.showScrollChip(response.getPackageName(), () -> onScrollButtonClicked(owner, response)); @@ -672,7 +673,7 @@ 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(); @@ -826,6 +832,7 @@ public class ScreenshotController { /** Reset screenshot view and then call onCompleteRunnable */ private void finishDismiss() { Log.d(TAG, "finishDismiss"); + mActionsController.endScreenshotSession(); mScrollCaptureExecutor.close(); if (mCurrentRequestCallback != null) { mCurrentRequestCallback.onFinish(); @@ -846,9 +853,8 @@ public class ScreenshotController { ImageExporter.Result result = future.get(); Log.d(TAG, "Saved screenshot: " + result); logScreenshotResultStatus(result.uri, screenshot.getUserHandle()); - mScreenshotHandler.resetTimeout(); if (result.uri != null) { - mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult( + mActionsController.setCompletedScreenshot(requestId, new ScreenshotSavedResult( result.uri, screenshot.getUserOrDefault(), result.timestamp)); } if (DEBUG_CALLBACK) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index 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/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 22aa492dbfe8..bf0843b8fa4e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -20,10 +20,12 @@ import android.content.Context import android.graphics.Rect import android.os.PowerManager import android.os.SystemClock +import android.util.ArraySet import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner @@ -35,6 +37,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.Flags.glanceableHubFullscreenSwipe import com.android.systemui.ambient.touch.TouchMonitor import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent import com.android.systemui.communal.dagger.Communal @@ -43,6 +46,7 @@ import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -51,10 +55,12 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.collectFlow +import java.util.function.Consumer import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch @@ -64,6 +70,7 @@ import kotlinx.coroutines.launch * * This will be used until the glanceable hub is integrated into Flexiglass. */ +@SysUISingleton class GlanceableHubContainerController @Inject constructor( @@ -75,11 +82,38 @@ constructor( private val communalColors: CommunalColors, private val ambientTouchComponentFactory: AmbientTouchComponent.Factory, private val communalContent: CommunalContent, - @Communal private val dataSourceDelegator: SceneDataSourceDelegator + @Communal private val dataSourceDelegator: SceneDataSourceDelegator, + private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController, ) : LifecycleOwner { + + private class CommunalWrapper(context: Context) : FrameLayout(context) { + private val consumers: MutableSet<Consumer<Boolean>> = ArraySet() + + override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + consumers.forEach { it.accept(disallowIntercept) } + super.requestDisallowInterceptTouchEvent(disallowIntercept) + } + + fun dispatchTouchEvent( + ev: MotionEvent?, + disallowInterceptConsumer: Consumer<Boolean>? + ): Boolean { + disallowInterceptConsumer?.apply { consumers.add(this) } + + try { + return super.dispatchTouchEvent(ev) + } finally { + consumers.clear() + } + } + } + /** The container view for the hub. This will not be initialized until [initView] is called. */ private var communalContainerView: View? = null + /** Wrapper around the communal container to intercept touch events */ + private var communalContainerWrapper: CommunalWrapper? = null + /** * This lifecycle is used to control when the [touchMonitor] listens to touches. The lifecycle * should only be [Lifecycle.State.RESUMED] when the hub is showing and not covered by anything, @@ -269,9 +303,13 @@ constructor( ) collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it }) - communalContainerView = containerView - - return containerView + if (glanceableHubFullscreenSwipe()) { + communalContainerWrapper = CommunalWrapper(containerView.context) + communalContainerWrapper?.addView(communalContainerView) + return communalContainerWrapper!! + } else { + return containerView + } } /** @@ -304,6 +342,11 @@ constructor( lifecycleRegistry.currentState = Lifecycle.State.CREATED communalContainerView = null } + + communalContainerWrapper?.let { + (it.parent as ViewGroup).removeView(it) + communalContainerWrapper = null + } } /** @@ -317,6 +360,18 @@ constructor( */ fun onTouchEvent(ev: MotionEvent): Boolean { SceneContainerFlag.assertInLegacyMode() + + // In the case that we are handling full swipes on the lockscreen, are on the lockscreen, + // and the touch is within the horizontal notification band on the screen, do not process + // the touch. + if ( + glanceableHubFullscreenSwipe() && + !hubShowing && + !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) + ) { + return false + } + return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false } @@ -328,12 +383,16 @@ constructor( val hubOccluded = anyBouncerShowing || shadeShowing if (isDown && !hubOccluded) { - val x = ev.rawX - val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth - if (inOpeningSwipeRegion || hubShowing) { - // Steal touch events when the hub is open, or if the touch started in the opening - // gesture region. + if (glanceableHubFullscreenSwipe()) { isTrackingHubTouch = true + } else { + val x = ev.rawX + val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth + if (inOpeningSwipeRegion || hubShowing) { + // Steal touch events when the hub is open, or if the touch started in the + // opening gesture region. + isTrackingHubTouch = true + } } } @@ -341,10 +400,7 @@ constructor( if (isUp || isCancel) { isTrackingHubTouch = false } - dispatchTouchEvent(view, ev) - // Return true regardless of dispatch result as some touches at the start of a gesture - // may return false from dispatchTouchEvent. - return true + return dispatchTouchEvent(view, ev) } return false @@ -354,13 +410,30 @@ constructor( * Dispatches the touch event to the communal container and sends a user activity event to reset * the screen timeout. */ - private fun dispatchTouchEvent(view: View, ev: MotionEvent) { - view.dispatchTouchEvent(ev) - powerManager.userActivity( - SystemClock.uptimeMillis(), - PowerManager.USER_ACTIVITY_EVENT_TOUCH, - 0 - ) + private fun dispatchTouchEvent(view: View, ev: MotionEvent): Boolean { + try { + var handled = false + if (glanceableHubFullscreenSwipe()) { + communalContainerWrapper?.dispatchTouchEvent(ev) { + if (it) { + handled = true + } + } + return handled || hubShowing + } else { + view.dispatchTouchEvent(ev) + // Return true regardless of dispatch result as some touches at the start of a + // gesture + // may return false from dispatchTouchEvent. + return true + } + } finally { + powerManager.userActivity( + SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH, + 0 + ) + } } override val lifecycle: Lifecycle diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index 6df8ac4c2145..4f6a64f043d2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -65,6 +65,7 @@ import com.android.internal.policy.SystemBarUtils; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.classifier.Classifier; +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.dump.DumpManager; @@ -157,6 +158,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum private final ShadeRepository mShadeRepository; private final ShadeInteractor mShadeInteractor; private final ActiveNotificationsInteractor mActiveNotificationsInteractor; + private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModelLazy; private final JavaAdapter mJavaAdapter; private final FalsingManager mFalsingManager; private final AccessibilityManager mAccessibilityManager; @@ -334,6 +336,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum JavaAdapter javaAdapter, CastController castController, SplitShadeStateController splitShadeStateController, + Lazy<CommunalTransitionViewModel> communalTransitionViewModelLazy, Lazy<LargeScreenHeaderHelper> largeScreenHeaderHelperLazy ) { SceneContainerFlag.assertInLegacyMode(); @@ -379,6 +382,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum mShadeRepository = shadeRepository; mShadeInteractor = shadeInteractor; mActiveNotificationsInteractor = activeNotificationsInteractor; + mCommunalTransitionViewModelLazy = communalTransitionViewModelLazy; mJavaAdapter = javaAdapter; mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback()); @@ -458,6 +462,9 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum initNotificationStackScrollLayoutController(); mJavaAdapter.alwaysCollectFlow( mShadeInteractor.isExpandToQsEnabled(), this::setExpansionEnabledPolicy); + mJavaAdapter.alwaysCollectFlow( + mCommunalTransitionViewModelLazy.get().isUmoOnCommunal(), + this::setShouldUpdateSquishinessOnMedia); } private void initNotificationStackScrollLayoutController() { @@ -892,6 +899,12 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum } } + private void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) { + if (mQs != null) { + mQs.setShouldUpdateSquishinessOnMedia(shouldUpdate); + } + } + void setOverScrollAmount(int overExpansion) { if (mQs != null) { mQs.setOverScrollAmount(overExpansion); 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/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 0d8030f02948..526800954427 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -19,9 +19,14 @@ package com.android.systemui.statusbar; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.AppGlobals; +import android.app.SynchronousUserSwitchObserver; +import android.app.UserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -37,6 +42,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManagerGlobal; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.text.Editable; @@ -136,6 +142,8 @@ public final class KeyboardShortcutListSearch { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final HandlerThread mHandlerThread = new HandlerThread("KeyboardShortcutHelper"); + @VisibleForTesting Handler mBackgroundHandler; @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; @@ -143,6 +151,13 @@ public final class KeyboardShortcutListSearch { private KeyCharacterMap mKeyCharacterMap; private KeyCharacterMap mBackupKeyCharacterMap; + private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { + @Override + public void onUserSwitching(int newUserId) throws RemoteException { + dismiss(); + } + }; + @VisibleForTesting KeyboardShortcutListSearch(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( @@ -413,36 +428,75 @@ public final class KeyboardShortcutListSearch { private boolean mAppShortcutsReceived; private boolean mImeShortcutsReceived; - @VisibleForTesting - public void showKeyboardShortcuts(int deviceId) { - retrieveKeyCharacterMap(deviceId); - mAppShortcutsReceived = false; - mImeShortcutsReceived = false; - mWindowManager.requestAppKeyboardShortcuts(result -> { - // Add specific app shortcuts + private void onAppSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { + // Add specific app shortcuts + if (result != null) { if (result.isEmpty()) { mCurrentAppPackageName = null; mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false); } else { mCurrentAppPackageName = result.get(0).getPackageName(); - mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result)); + if (validateKeyboardShortcutHelperIconUri()) { + KeyboardShortcuts.sanitiseShortcuts(result); + } + mSpecificAppGroup.addAll( + reMapToKeyboardShortcutMultiMappingGroup(result)); mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true); } - mAppShortcutsReceived = true; - if (mImeShortcutsReceived) { - mergeAndShowKeyboardShortcutsGroups(); - } - }, deviceId); - mWindowManager.requestImeKeyboardShortcuts(result -> { - // Add specific Ime shortcuts + } + mAppShortcutsReceived = true; + if (mImeShortcutsReceived) { + mergeAndShowKeyboardShortcutsGroups(); + } + } + + private void onImeSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { + // Add specific Ime shortcuts + if (result != null) { if (!result.isEmpty()) { - mInputGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result)); + if (validateKeyboardShortcutHelperIconUri()) { + KeyboardShortcuts.sanitiseShortcuts(result); + } + mInputGroup.addAll( + reMapToKeyboardShortcutMultiMappingGroup(result)); } - mImeShortcutsReceived = true; - if (mAppShortcutsReceived) { - mergeAndShowKeyboardShortcutsGroups(); + } + mImeShortcutsReceived = true; + if (mAppShortcutsReceived) { + mergeAndShowKeyboardShortcutsGroups(); + } + } + + @VisibleForTesting + public void showKeyboardShortcuts(int deviceId) { + if (mBackgroundHandler == null) { + mHandlerThread.start(); + mBackgroundHandler = new Handler(mHandlerThread.getLooper()); + } + + if (validateKeyboardShortcutHelperIconUri()) { + try { + ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); + } catch (RemoteException e) { + Log.e(TAG, "could not register user switch observer", e); } - }, deviceId); + } + + retrieveKeyCharacterMap(deviceId); + mAppShortcutsReceived = false; + mImeShortcutsReceived = false; + mWindowManager.requestAppKeyboardShortcuts( + result -> { + mBackgroundHandler.post(() -> { + onAppSpecificShortcutsReceived(result); + }); + }, deviceId); + mWindowManager.requestImeKeyboardShortcuts( + result -> { + mBackgroundHandler.post(() -> { + onImeSpecificShortcutsReceived(result); + }); + }, deviceId); } private void mergeAndShowKeyboardShortcutsGroups() { @@ -508,6 +562,14 @@ public final class KeyboardShortcutListSearch { mKeyboardShortcutsBottomSheetDialog.dismiss(); mKeyboardShortcutsBottomSheetDialog = null; } + mHandlerThread.quit(); + if (validateKeyboardShortcutHelperIconUri()) { + try { + ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); + } catch (RemoteException e) { + Log.e(TAG, "Could not unregister user switch observer", e); + } + } } private KeyboardShortcutMultiMappingGroup getMultiMappingSystemShortcuts(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index 21f608e13f5c..d00916a1c1a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -20,11 +20,16 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.AppGlobals; import android.app.Dialog; +import android.app.SynchronousUserSwitchObserver; +import android.app.UserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; @@ -39,6 +44,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManager; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.util.Log; @@ -93,6 +99,8 @@ public final class KeyboardShortcuts { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final HandlerThread mHandlerThread = new HandlerThread("KeyboardShortcutHelper"); + @VisibleForTesting Handler mBackgroundHandler; @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() { @@ -129,6 +137,13 @@ public final class KeyboardShortcuts { @Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null; @Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null; + private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { + @Override + public void onUserSwitching(int newUserId) throws RemoteException { + dismiss(); + } + }; + @VisibleForTesting KeyboardShortcuts(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( @@ -374,21 +389,68 @@ public final class KeyboardShortcuts { @VisibleForTesting public void showKeyboardShortcuts(int deviceId) { + if (mBackgroundHandler == null) { + mHandlerThread.start(); + mBackgroundHandler = new Handler(mHandlerThread.getLooper()); + } + + if (validateKeyboardShortcutHelperIconUri()) { + try { + ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); + } catch (RemoteException e) { + Log.e(TAG, "could not register user switch observer", e); + } + } + retrieveKeyCharacterMap(deviceId); + mReceivedAppShortcutGroups = null; mReceivedImeShortcutGroups = null; + mWindowManager.requestAppKeyboardShortcuts( result -> { - mReceivedAppShortcutGroups = result; - maybeMergeAndShowKeyboardShortcuts(); + mBackgroundHandler.post(() -> { + onAppSpecificShortcutsReceived(result); + }); }, deviceId); mWindowManager.requestImeKeyboardShortcuts( result -> { - mReceivedImeShortcutGroups = result; - maybeMergeAndShowKeyboardShortcuts(); + mBackgroundHandler.post(() -> { + onImeSpecificShortcutsReceived(result); + }); }, deviceId); } + private void onAppSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { + mReceivedAppShortcutGroups = + result == null ? Collections.emptyList() : result; + + if (validateKeyboardShortcutHelperIconUri()) { + sanitiseShortcuts(mReceivedAppShortcutGroups); + } + + maybeMergeAndShowKeyboardShortcuts(); + } + + private void onImeSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { + mReceivedImeShortcutGroups = + result == null ? Collections.emptyList() : result; + + if (validateKeyboardShortcutHelperIconUri()) { + sanitiseShortcuts(mReceivedImeShortcutGroups); + } + + maybeMergeAndShowKeyboardShortcuts(); + } + + static void sanitiseShortcuts(List<KeyboardShortcutGroup> shortcutGroups) { + for (KeyboardShortcutGroup group : shortcutGroups) { + for (KeyboardShortcutInfo info : group.getItems()) { + info.clearIcon(); + } + } + } + private void maybeMergeAndShowKeyboardShortcuts() { if (mReceivedAppShortcutGroups == null || mReceivedImeShortcutGroups == null) { return; @@ -413,6 +475,14 @@ public final class KeyboardShortcuts { mKeyboardShortcutsDialog.dismiss(); mKeyboardShortcutsDialog = null; } + mHandlerThread.quit(); + if (validateKeyboardShortcutHelperIconUri()) { + try { + ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); + } catch (RemoteException e) { + Log.e(TAG, "Could not unregister user switch observer", e); + } + } } private KeyboardShortcutGroup getSystemShortcuts() { 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/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java index 3cf61e211e42..8d3f7284e359 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java @@ -362,20 +362,34 @@ public class NotificationGroupingUtil { } protected boolean hasSameIcon(Object parentData, Object childData) { - Icon parentIcon = ((Notification) parentData).getSmallIcon(); - Icon childIcon = ((Notification) childData).getSmallIcon(); + Icon parentIcon = getIcon((Notification) parentData); + Icon childIcon = getIcon((Notification) childData); return parentIcon.sameAs(childIcon); } + private static Icon getIcon(Notification notification) { + if (notification.shouldUseAppIcon()) { + return notification.getAppIcon(); + } + return notification.getSmallIcon(); + } + /** * @return whether two ImageViews have the same colorFilterSet or none at all */ protected boolean hasSameColor(Object parentData, Object childData) { - int parentColor = ((Notification) parentData).color; - int childColor = ((Notification) childData).color; + int parentColor = getColor((Notification) parentData); + int childColor = getColor((Notification) childData); return parentColor == childColor; } + private static int getColor(Notification notification) { + if (notification.shouldUseAppIcon()) { + return 0; // the color filter isn't applied if using the app icon + } + return notification.color; + } + @Override public boolean isEmpty(View view) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 5bf2f41ae453..6d34a0fa9c24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -15,7 +15,8 @@ */ package com.android.systemui.statusbar; -import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss; +import static com.android.systemui.Flags.mediaControlsUserInitiatedDeleteintent; +import static com.android.systemui.Flags.notificationMediaManagerBackgroundExecution; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,12 +27,16 @@ import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; +import android.os.Handler; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.util.Log; +import androidx.annotation.VisibleForTesting; + import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; import com.android.systemui.media.controls.shared.model.MediaData; @@ -48,6 +53,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.Executor; @@ -80,13 +86,16 @@ public class NotificationMediaManager implements Dumpable { private final ArrayList<MediaListener> mMediaListeners; private final Executor mBackgroundExecutor; + private final Handler mHandler; protected NotificationPresenter mPresenter; - private MediaController mMediaController; + @VisibleForTesting + MediaController mMediaController; private String mMediaNotificationKey; private MediaMetadata mMediaMetadata; - private final MediaController.Callback mMediaListener = new MediaController.Callback() { + @VisibleForTesting + final MediaController.Callback mMediaListener = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { super.onPlaybackStateChanged(state); @@ -107,11 +116,20 @@ public class NotificationMediaManager implements Dumpable { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } - mMediaMetadata = metadata; + if (notificationMediaManagerBackgroundExecution()) { + mBackgroundExecutor.execute(() -> setMediaMetadata(metadata)); + } else { + setMediaMetadata(metadata); + } + dispatchUpdateMediaMetaData(); } }; + private void setMediaMetadata(MediaMetadata metadata) { + mMediaMetadata = metadata; + } + /** * Injected constructor. See {@link CentralSurfacesModule}. */ @@ -122,7 +140,9 @@ public class NotificationMediaManager implements Dumpable { NotifCollection notifCollection, MediaDataManager mediaDataManager, DumpManager dumpManager, - @Background Executor backgroundExecutor) { + @Background Executor backgroundExecutor, + @Main Handler handler + ) { mContext = context; mMediaListeners = new ArrayList<>(); mVisibilityProvider = visibilityProvider; @@ -130,6 +150,7 @@ public class NotificationMediaManager implements Dumpable { mNotifPipeline = notifPipeline; mNotifCollection = notifCollection; mBackgroundExecutor = backgroundExecutor; + mHandler = handler; setupNotifPipeline(); @@ -178,7 +199,7 @@ public class NotificationMediaManager implements Dumpable { @Override public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) { - if (mediaControlsUserInitiatedDismiss() && !userInitiated) { + if (mediaControlsUserInitiatedDeleteintent() && !userInitiated) { // Dismissing the notification will send the app's deleteIntent, so ignore if // this was an automatic removal Log.d(TAG, "Not dismissing " + key + " because it was removed by the system"); @@ -262,6 +283,14 @@ public class NotificationMediaManager implements Dumpable { public void addCallback(MediaListener callback) { mMediaListeners.add(callback); + if (notificationMediaManagerBackgroundExecution()) { + mBackgroundExecutor.execute(() -> updateMediaMetaData(callback)); + } else { + updateMediaMetaData(callback); + } + } + + private void updateMediaMetaData(MediaListener callback) { callback.onPrimaryMetadataOrStateChanged(mMediaMetadata, getMediaControllerPlaybackState(mMediaController)); } @@ -273,7 +302,11 @@ public class NotificationMediaManager implements Dumpable { public void findAndUpdateMediaNotifications() { // TODO(b/169655907): get the semi-filtered notifications for current user Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs(); - findPlayingMediaNotification(allNotifications); + if (notificationMediaManagerBackgroundExecution()) { + mBackgroundExecutor.execute(() -> findPlayingMediaNotification(allNotifications)); + } else { + findPlayingMediaNotification(allNotifications); + } dispatchUpdateMediaMetaData(); } @@ -312,7 +345,7 @@ public class NotificationMediaManager implements Dumpable { // We have a new media session clearCurrentMediaNotificationSession(); mMediaController = controller; - mMediaController.registerCallback(mMediaListener); + mMediaController.registerCallback(mMediaListener, mHandler); mMediaMetadata = mMediaController.getMetadata(); if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: " @@ -331,13 +364,29 @@ public class NotificationMediaManager implements Dumpable { } public void clearCurrentMediaNotification() { + if (notificationMediaManagerBackgroundExecution()) { + mBackgroundExecutor.execute(this::clearMediaNotification); + } else { + clearMediaNotification(); + } + } + + private void clearMediaNotification() { mMediaNotificationKey = null; clearCurrentMediaNotificationSession(); } private void dispatchUpdateMediaMetaData() { - @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController); ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners); + if (notificationMediaManagerBackgroundExecution()) { + mBackgroundExecutor.execute(() -> updateMediaMetaData(callbacks)); + } else { + updateMediaMetaData(callbacks); + } + } + + private void updateMediaMetaData(List<MediaListener> callbacks) { + @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController); for (int i = 0; i < callbacks.size(); i++) { callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state); } @@ -393,7 +442,6 @@ public class NotificationMediaManager implements Dumpable { Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " + mMediaController.getPackageName()); } - // TODO(b/336612071): move to background thread mMediaController.unregisterCallback(mMediaListener); } mMediaController = null; 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/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt new file mode 100644 index 000000000000..ac16d26e415c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt @@ -0,0 +1,79 @@ +/* + * 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.mediaprojection.domain.interactor + +import com.android.systemui.common.shared.model.ContentDescription +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.mediaprojection.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository +import com.android.systemui.res.R +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.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * Interactor for media-projection-related chips in the status bar. + * + * There are two kinds of media projection events that will show chips in the status bar: + * 1) Share-to-app: Sharing your phone screen content to another app on the same device. (Triggered + * from within each individual app.) + * 2) Cast-to-other-device: Sharing your phone screen content to a different device. (Triggered from + * the Quick Settings Cast tile or from the Settings app.) This interactor handles both of those + * event types (though maybe not audio-only casting -- see b/342169876). + */ +@SysUISingleton +class MediaProjectionChipInteractor +@Inject +constructor( + @Application scope: CoroutineScope, + mediaProjectionRepository: MediaProjectionRepository, + val systemClock: SystemClock, +) : OngoingActivityChipInteractor { + override val chip: StateFlow<OngoingActivityChipModel> = + mediaProjectionRepository.mediaProjectionState + .map { state -> + when (state) { + is MediaProjectionState.NotProjecting -> OngoingActivityChipModel.Hidden + is MediaProjectionState.EntireScreen, + is MediaProjectionState.SingleTask -> { + // TODO(b/332662551): Distinguish between cast-to-other-device and + // share-to-app. + OngoingActivityChipModel.Shown( + icon = + Icon.Resource( + R.drawable.ic_cast_connected, + ContentDescription.Resource(R.string.accessibility_casting) + ), + // TODO(b/332662551): See if we can use a MediaProjection API to fetch + // this time. + 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/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt index 6f16969b1bb6..585ff5f78ff8 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. +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/binder/ChipChronometerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.kt new file mode 100644 index 000000000000..2032ec8af78c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/ChipChronometerBinder.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.statusbar.chips.ui.binder + +import com.android.systemui.statusbar.chips.ui.view.ChipChronometer + +object ChipChronometerBinder { + /** + * Updates the given [view] chronometer with a new start time and starts it. + * + * @param startTimeMs the time this event started, relative to + * [com.android.systemui.util.time.SystemClock.elapsedRealtime]. See + * [android.widget.Chronometer.setBase]. + */ + fun bind(startTimeMs: Long, view: ChipChronometer) { + view.base = startTimeMs + view.start() + } +} 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..edb2983720a8 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,9 @@ 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.mediaprojection.domain.interactor.MediaProjectionChipInteractor 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 @@ -40,6 +41,7 @@ class OngoingActivityChipsViewModel constructor( @Application scope: CoroutineScope, screenRecordChipInteractor: ScreenRecordChipInteractor, + mediaProjectionChipInteractor: MediaProjectionChipInteractor, callChipInteractor: CallChipInteractor, ) { @@ -51,10 +53,19 @@ constructor( * actually displaying the chip. */ val chip: StateFlow<OngoingActivityChipModel> = - combine(screenRecordChipInteractor.chip, callChipInteractor.chip) { screenRecord, call -> + combine( + screenRecordChipInteractor.chip, + mediaProjectionChipInteractor.chip, + callChipInteractor.chip + ) { screenRecord, mediaProjection, call -> // This `when` statement shows the priority order of the chips when { + // Screen recording also activates the media projection APIs, so whenever the + // screen recording chip is active, the media projection chip would also be + // active. We want the screen-recording-specific chip shown in this case, so we + // give the screen recording chip priority. See b/296461748. screenRecord is OngoingActivityChipModel.Shown -> screenRecord + mediaProjection is OngoingActivityChipModel.Shown -> mediaProjection else -> call } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 05245898c161..7df7ef187e26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.dagger; import static com.android.systemui.Flags.predictiveBackAnimateDialogs; import android.content.Context; +import android.os.Handler; import android.os.RemoteException; import android.service.dreams.IDreamManager; import android.util.Log; @@ -99,7 +100,8 @@ public interface CentralSurfacesDependenciesModule { NotifCollection notifCollection, MediaDataManager mediaDataManager, DumpManager dumpManager, - @Background Executor backgroundExecutor) { + @Background Executor backgroundExecutor, + @Main Handler handler) { return new NotificationMediaManager( context, visibilityProvider, @@ -107,7 +109,8 @@ public interface CentralSurfacesDependenciesModule { notifCollection, mediaDataManager, dumpManager, - backgroundExecutor); + backgroundExecutor, + handler); } /** */ 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/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java index 5bbd77ee6dd9..60d846ebacac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AppGlobals; @@ -271,13 +272,16 @@ public class InstantAppNotifier .addFlags(Intent.FLAG_IGNORE_EPHEMERAL) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + ActivityOptions options = ActivityOptions.makeBasic() + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); PendingIntent pendingIntent = PendingIntent.getActivityAsUser( mContext, 0 /* requestCode */, browserIntent, PendingIntent.FLAG_IMMUTABLE /* flags */, - null, + options.toBundle(), user); ComponentName aiaComponent = null; try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt index 319b49972bd2..16d0cc42db7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt @@ -18,25 +18,27 @@ package com.android.systemui.statusbar.notification.icon import android.app.Notification import android.content.Context +import android.graphics.drawable.Drawable import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.contentDescForNotification import javax.inject.Inject -/** - * Testable wrapper around Context. - */ -class IconBuilder @Inject constructor( - private val context: Context -) { +/** Testable wrapper around Context. */ +class IconBuilder @Inject constructor(private val context: Context) { fun createIconView(entry: NotificationEntry): StatusBarIconView { return StatusBarIconView( - context, - "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}", - entry.sbn) + context, + "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}", + entry.sbn + ) } fun getIconContentDescription(n: Notification): CharSequence { return contentDescForNotification(context, n) } + + fun getAppIcon(n: Notification): Drawable { + return n.loadHeaderAppIcon(context) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index 271b0a86ca12..3df9374da914 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -20,6 +20,8 @@ import android.app.Notification import android.app.Notification.MessagingStyle import android.app.Person import android.content.pm.LauncherApps +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.os.Build import android.os.Bundle @@ -165,7 +167,7 @@ constructor( Log.wtf( TAG, "Updating using the cache is not supported when the " + - "notifications_background_conversation_icons flag is off" + "notifications_background_icons flag is off" ) } if (!usingCache || !Flags.notificationsBackgroundIcons()) { @@ -216,39 +218,85 @@ constructor( @Throws(InflationException::class) private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon { - val n = entry.sbn.notification val showPeopleAvatar = !redact && isImportantConversation(entry) + // If the descriptor is already cached, return it + getCachedIconDescriptor(entry, showPeopleAvatar)?.also { + return it + } + + val n = entry.sbn.notification + var usingMonochromeAppIcon = false + val icon: Icon? + if (showPeopleAvatar) { + icon = createPeopleAvatar(entry) + } else if (android.app.Flags.notificationsUseMonochromeAppIcon()) { + if (n.shouldUseAppIcon()) { + icon = + getMonochromeAppIcon(entry)?.also { usingMonochromeAppIcon = true } + ?: n.smallIcon + } else { + icon = n.smallIcon + } + } else { + icon = n.smallIcon + } + + if (icon == null) { + throw InflationException("No icon in notification from ${entry.sbn.packageName}") + } + + val sbi = icon.toStatusBarIcon(entry) + cacheIconDescriptor(entry, sbi, showPeopleAvatar, usingMonochromeAppIcon) + return sbi + } + + private fun getCachedIconDescriptor( + entry: NotificationEntry, + showPeopleAvatar: Boolean + ): StatusBarIcon? { val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor + val appIconDescriptor = entry.icons.appIconDescriptor val smallIconDescriptor = entry.icons.smallIconDescriptor // If cached, return corresponding cached values - if (showPeopleAvatar && peopleAvatarDescriptor != null) { - return peopleAvatarDescriptor - } else if (!showPeopleAvatar && smallIconDescriptor != null) { - return smallIconDescriptor + return when { + showPeopleAvatar && peopleAvatarDescriptor != null -> peopleAvatarDescriptor + android.app.Flags.notificationsUseMonochromeAppIcon() && appIconDescriptor != null -> + appIconDescriptor + smallIconDescriptor != null -> smallIconDescriptor + else -> null } + } - val icon = - (if (showPeopleAvatar) { - createPeopleAvatar(entry) + private fun cacheIconDescriptor( + entry: NotificationEntry, + descriptor: StatusBarIcon, + showPeopleAvatar: Boolean, + usingMonochromeAppIcon: Boolean + ) { + if (android.app.Flags.notificationsUseAppIcon() || + android.app.Flags.notificationsUseMonochromeAppIcon() + ) { + // If either of the new icon flags is enabled, we cache the icon all the time. + if (showPeopleAvatar) { + entry.icons.peopleAvatarDescriptor = descriptor + } else if (usingMonochromeAppIcon) { + // When notificationsUseMonochromeAppIcon is enabled, we use the appIconDescriptor. + entry.icons.appIconDescriptor = descriptor } else { - n.smallIcon - }) - ?: throw InflationException("No icon in notification from " + entry.sbn.packageName) - - val sbi = icon.toStatusBarIcon(entry) - - // Cache if important conversation or app icon. - if (isImportantConversation(entry) || android.app.Flags.notificationsUseAppIcon()) { + // When notificationsUseAppIcon is enabled, the app icon overrides the small icon. + // But either way, it's a good idea to cache the descriptor. + entry.icons.smallIconDescriptor = descriptor + } + } else if (isImportantConversation(entry)) { + // Old approach: cache only if important conversation. if (showPeopleAvatar) { - entry.icons.peopleAvatarDescriptor = sbi + entry.icons.peopleAvatarDescriptor = descriptor } else { - entry.icons.smallIconDescriptor = sbi + entry.icons.smallIconDescriptor = descriptor } } - - return sbi } @Throws(InflationException::class) @@ -276,6 +324,29 @@ constructor( ) } + // TODO(b/335211019): Should we merge this with the method in GroupHelper? + private fun getMonochromeAppIcon(entry: NotificationEntry): Icon? { + // TODO(b/335211019): This should be done in the background. + var monochromeIcon: Icon? = null + try { + val appIcon: Drawable = iconBuilder.getAppIcon(entry.sbn.notification) + if (appIcon is AdaptiveIconDrawable) { + if (appIcon.monochrome != null) { + monochromeIcon = + Icon.createWithResourceAdaptiveDrawable( + /* resPackage = */ entry.sbn.packageName, + /* resId = */ appIcon.sourceDrawableResId, + /* useMonochrome = */ true, + /* inset = */ -3.0f * AdaptiveIconDrawable.getExtraInsetFraction() + ) + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to getAppIcon() in getMonochromeAppIcon()", e) + } + return monochromeIcon + } + private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) = withContext(bgCoroutineContext) { var icon: Icon? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java index 442c0978fd77..d029ce722af9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java @@ -33,6 +33,7 @@ public final class IconPack { @Nullable private final StatusBarIconView mAodIcon; @Nullable private StatusBarIcon mSmallIconDescriptor; + @Nullable private StatusBarIcon mAppIconDescriptor; @Nullable private StatusBarIcon mPeopleAvatarDescriptor; private boolean mIsImportantConversation; @@ -111,6 +112,15 @@ public final class IconPack { mPeopleAvatarDescriptor = peopleAvatarDescriptor; } + @Nullable + StatusBarIcon getAppIconDescriptor() { + return mAppIconDescriptor; + } + + void setAppIconDescriptor(@Nullable StatusBarIcon appIconDescriptor) { + mAppIconDescriptor = appIconDescriptor; + } + boolean isImportantConversation() { return mIsImportantConversation; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt index c74c396741d7..c29d700396af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt @@ -21,9 +21,9 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.util.Log +import com.android.internal.logging.UiEventLogger import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.util.time.SystemClock import javax.inject.Inject // Class to track avalanche trigger event time. @@ -33,6 +33,7 @@ class AvalancheProvider constructor( private val broadcastDispatcher: BroadcastDispatcher, private val logger: VisualInterruptionDecisionLogger, + private val uiEventLogger: UiEventLogger, ) { val TAG = "AvalancheProvider" val timeoutMs = 120000 @@ -56,6 +57,7 @@ constructor( return } Log.d(TAG, "broadcastReceiver received intent.action=" + intent.action) + uiEventLogger.log(AvalancheSuppressor.AvalancheEvent.START); startTime = System.currentTimeMillis() } } 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..42a5bdf0f19b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -33,6 +33,8 @@ import android.os.PowerManager import android.provider.Settings import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.UiEvent; import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker @@ -48,6 +50,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 +124,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 + } } } @@ -232,12 +243,12 @@ class AlertKeyguardVisibilitySuppressor( override fun shouldSuppress(entry: NotificationEntry) = keyguardNotificationVisibilityProvider.shouldHideNotification(entry) } - class AvalancheSuppressor( private val avalancheProvider: AvalancheProvider, private val systemClock: SystemClock, private val systemSettings: SystemSettings, private val packageManager: PackageManager, + private val uiEventLogger: UiEventLogger, ) : VisualInterruptionFilter( types = setOf(PEEK, PULSE), @@ -257,6 +268,44 @@ class AvalancheSuppressor( SUPPRESS } + enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "An avalanche event occurred but this notification was suppressed by a " + + "non-avalanche suppressor.") + START(1802), + + @UiEvent(doc = "HUN was suppressed in avalanche.") + SUPPRESS(1803), + + @UiEvent(doc = "HUN allowed during avalanche because it is high priority.") + ALLOW_CONVERSATION_AFTER_AVALANCHE(1804), + + @UiEvent(doc = "HUN allowed during avalanche because it is a high priority conversation.") + ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME(1805), + + @UiEvent(doc = "HUN allowed during avalanche because it is a call.") + ALLOW_CALLSTYLE(1806), + + @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.") + ALLOW_CATEGORY_REMINDER(1807), + + @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.") + ALLOW_CATEGORY_EVENT(1808), + + @UiEvent(doc = "HUN allowed during avalanche because it has a full screen intent and " + + "the full screen intent permission is granted.") + ALLOW_FSI_WITH_PERMISSION_ON(1809), + + @UiEvent(doc = "HUN allowed during avalanche because it is colorized.") + ALLOW_COLORIZED(1810), + + @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.") + ALLOW_EMERGENCY(1811); + + override fun getId(): Int { + return id + } + } + override fun shouldSuppress(entry: NotificationEntry): Boolean { if (!isCooldownEnabled()) { return false @@ -278,41 +327,46 @@ class AvalancheSuppressor( entry.ranking.isConversation && entry.sbn.notification.getWhen() > avalancheProvider.startTime ) { + uiEventLogger.log(AvalancheEvent.ALLOW_CONVERSATION_AFTER_AVALANCHE) return State.ALLOW_CONVERSATION_AFTER_AVALANCHE } if (entry.channel?.isImportantConversation == true) { + uiEventLogger.log(AvalancheEvent.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME) return State.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME } if (entry.sbn.notification.isStyle(Notification.CallStyle::class.java)) { + uiEventLogger.log(AvalancheEvent.ALLOW_CALLSTYLE) return State.ALLOW_CALLSTYLE } if (entry.sbn.notification.category == CATEGORY_REMINDER) { + uiEventLogger.log(AvalancheEvent.ALLOW_CATEGORY_REMINDER) return State.ALLOW_CATEGORY_REMINDER } if (entry.sbn.notification.category == CATEGORY_EVENT) { + uiEventLogger.log(AvalancheEvent.ALLOW_CATEGORY_EVENT) return State.ALLOW_CATEGORY_EVENT } if (entry.sbn.notification.fullScreenIntent != null) { + uiEventLogger.log(AvalancheEvent.ALLOW_FSI_WITH_PERMISSION_ON) return State.ALLOW_FSI_WITH_PERMISSION_ON } - - if (entry.sbn.notification.isColorized) { - return State.ALLOW_COLORIZED - } if (entry.sbn.notification.isColorized) { + uiEventLogger.log(AvalancheEvent.ALLOW_COLORIZED) return State.ALLOW_COLORIZED } if ( packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) == PERMISSION_GRANTED ) { + uiEventLogger.log(AvalancheEvent.ALLOW_EMERGENCY) return State.ALLOW_EMERGENCY } + uiEventLogger.log(AvalancheEvent.SUPPRESS) return State.SUPPRESS } 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..84f8662f5fee 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)) @@ -175,7 +178,8 @@ constructor( if (NotificationAvalancheSuppression.isEnabled) { addFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) avalancheProvider.register() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index edd2961fe119..99ce454126cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2843,14 +2843,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mIsSystemChildExpanded = expanded; } - public void setLayoutListener(LayoutListener listener) { + public void setLayoutListener(@Nullable LayoutListener listener) { mLayoutListener = listener; } - public void removeListener() { - mLayoutListener = null; - } - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout")); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index c10c09c49c6b..bdfbc4b53943 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -242,7 +242,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl public void onLayout() { mIconsPlaced = false; // Force icons to be re-placed setMenuLocation(); - mParent.removeListener(); + mParent.setLayoutListener(null); } private void createMenuViews(boolean resetState) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index bd7f766c6860..d1fabb168d90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -191,8 +191,12 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple updateTransformedTypes(); addRemainingTransformTypes(); updateCropToPaddingForImageViews(); - Notification notification = row.getEntry().getSbn().getNotification(); - mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon()); + Notification n = row.getEntry().getSbn().getNotification(); + if (n.shouldUseAppIcon()) { + mIcon.setTag(ImageTransformState.ICON_TAG, n.getAppIcon()); + } else { + mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon()); + } // We need to reset all views that are no longer transforming in case a view was previously // transformed, but now we decided to transform its container instead. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt index d6c73a9dda9e..2dccea668916 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt @@ -16,24 +16,22 @@ package com.android.systemui.statusbar.notification.shared -import com.android.systemui.Flags import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils /** Helper for reading or using the heads-up cycling flag state. */ @Suppress("NOTHING_TO_INLINE") object NotificationHeadsUpCycling { - /** The aconfig flag name - enable this feature when FLAG_NOTIFICATION_THROTTLE_HUN is on. */ - const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN + /** The aconfig flag name */ + const val FLAG_NAME = NotificationThrottleHun.FLAG_NAME /** A token used for dependency declaration */ val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) + get() = NotificationThrottleHun.token /** Is the heads-up cycling animation enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationThrottleHun() + get() = NotificationThrottleHun.isEnabled /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */ @JvmStatic @@ -46,13 +44,12 @@ object NotificationHeadsUpCycling { * build to ensure that the refactor author catches issues in testing. */ @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + inline fun isUnexpectedlyInLegacyMode() = NotificationThrottleHun.isUnexpectedlyInLegacyMode() /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if * the flag is enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) + inline fun assertInLegacyMode() = NotificationThrottleHun.assertInLegacyMode() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt index dd81d42b58ee..71f0de08ece3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt @@ -24,7 +24,7 @@ import com.android.systemui.flags.RefactorFlagUtils @Suppress("NOTHING_TO_INLINE") object NotificationThrottleHun { /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +33,7 @@ object NotificationThrottleHun { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationThrottleHun() + get() = Flags.notificationAvalancheThrottleHun() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index a9d7cc003b79..fe22cc628b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -24,7 +24,6 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; import static com.android.systemui.Flags.newAodTransition; import static com.android.systemui.Flags.notificationOverExpansionClippingFix; -import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.DumpUtilsKt.println; @@ -255,7 +254,8 @@ public class NotificationStackScrollLayout * The raw amount of the overScroll on the bottom, which is not rubber-banded. */ private float mOverScrolledBottomPixels; - private ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>(); + private final ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>(); + private final ListenerSet<Runnable> mHeadsUpHeightChangedListeners = new ListenerSet<>(); private NotificationLogger.OnChildLocationsChangedListener mListener; private OnNotificationLocationsChangedListener mLocationsChangedListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; @@ -1114,6 +1114,28 @@ public class NotificationStackScrollLayout mStackHeightChangedListeners.remove(runnable); } + private void notifyHeadsUpHeightChangedForView(View view) { + if (mTopHeadsUpRow == view) { + notifyHeadsUpHeightChangedListeners(); + } + } + + private void notifyHeadsUpHeightChangedListeners() { + for (Runnable listener : mHeadsUpHeightChangedListeners) { + listener.run(); + } + } + + @Override + public void addHeadsUpHeightChangedListener(@NonNull Runnable runnable) { + mHeadsUpHeightChangedListeners.addIfAbsent(runnable); + } + + @Override + public void removeHeadsUpHeightChangedListener(@NonNull Runnable runnable) { + mHeadsUpHeightChangedListeners.remove(runnable); + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (!mSuppressChildrenMeasureAndLayout) { @@ -2444,6 +2466,11 @@ public class NotificationStackScrollLayout return mScrollViewFields.getIntrinsicStackHeight(); } + @Override + public int getTopHeadsUpHeight() { + return getTopHeadsUpPinnedHeight(); + } + /** * Calculate the gap height between two different views * @@ -2816,23 +2843,15 @@ public class NotificationStackScrollLayout mAddedHeadsUpChildren.remove(child); return false; } - if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) { - // Skip adding animation for clicked heads up notifications when the - // Shade is closed, because the animation event is generated in - // generateHeadsUpAnimationEvents. Only report that an animation was - // actually generated (thus requesting the transient view be added) - // if a removal animation is in progress. - if (!isExpanded() && isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return child.inRemovalAnimation(); - } - } else { - if (isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return true; - } + // Skip adding animation for clicked heads up notifications when the + // Shade is closed, because the animation event is generated in + // generateHeadsUpAnimationEvents. Only report that an animation was + // actually generated (thus requesting the transient view be added) + // if a removal animation is in progress. + if (!isExpanded() && isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return child.inRemovalAnimation(); } if (mDebugRemoveAnimation) { Log.d(TAG, "generateRemove " + key @@ -4193,12 +4212,14 @@ public class NotificationStackScrollLayout requestAnimationOnViewResize(row); } requestChildrenUpdate(); + notifyHeadsUpHeightChangedForView(view); mAnimateStackYForContentHeightChange = previouslyNeededAnimation; } void onChildHeightReset(ExpandableView view) { updateAnimationState(view); updateChronometerForChild(view); + notifyHeadsUpHeightChangedForView(view); } private void updateScrollPositionOnExpandInBottom(ExpandableView view) { @@ -5573,6 +5594,7 @@ public class NotificationStackScrollLayout */ public void setTopHeadsUpRow(@Nullable ExpandableNotificationRow topHeadsUpRow) { mTopHeadsUpRow = topHeadsUpRow; + notifyHeadsUpHeightChangedListeners(); } public boolean getIsExpanded() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt index db544ce59aa1..f6722a4ccff0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt @@ -38,9 +38,6 @@ class NotificationPlaceholderRepository @Inject constructor() { */ val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null) - /** the y position of the top of the HUN area */ - val headsUpTop = MutableStateFlow(0f) - /** height made available to the notifications in the size-constrained mode of lock screen. */ val constrainedAvailableSpace = MutableStateFlow(0) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt index 463c631db32f..f6d9351952f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt @@ -27,9 +27,6 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class NotificationViewHeightRepository @Inject constructor() { - /** The height in px of the current heads up notification. */ - val headsUpHeight = MutableStateFlow(0f) - /** * The amount in px that the notification stack should scroll due to internal expansion. This * should only happen when a notification expansion hits the bottom of the screen, so it is diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index e7acbe3ab0b0..8557afc6ebd3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -65,9 +65,6 @@ constructor( } .distinctUntilChanged() - /** The height in px of the contents of the HUN. */ - val headsUpHeight: StateFlow<Float> = viewHeightRepository.headsUpHeight.asStateFlow() - /** The alpha of the Notification Stack for the brightness mirror */ val alphaForBrightnessMirror: StateFlow<Float> = placeholderRepository.alphaForBrightnessMirror.asStateFlow() @@ -82,9 +79,6 @@ constructor( */ val scrolledToTop: StateFlow<Boolean> = placeholderRepository.scrolledToTop.asStateFlow() - /** The y-coordinate in px of bottom of the contents of the HUN. */ - val headsUpTop: StateFlow<Float> = placeholderRepository.headsUpTop.asStateFlow() - /** * The amount in px that the notification stack should scroll due to internal expansion. This * should only happen when a notification expansion hits the bottom of the screen, so it is @@ -110,11 +104,6 @@ constructor( placeholderRepository.shadeScrimBounds.value = bounds } - /** Sets the height of heads up notification. */ - fun setHeadsUpHeight(height: Float) { - viewHeightRepository.headsUpHeight.value = height - } - /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { placeholderRepository.scrolledToTop.value = scrolledToTop @@ -133,8 +122,4 @@ constructor( fun setConstrainedAvailableSpace(height: Int) { placeholderRepository.constrainedAvailableSpace.value = height } - - fun setHeadsUpTop(headsUpTop: Float) { - placeholderRepository.headsUpTop.value = headsUpTop - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 14b882f974d2..eaaa9a1523c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -32,6 +32,9 @@ interface NotificationScrollView { */ val intrinsicStackHeight: Int + /** Height in pixels required to display the top HeadsUp Notification. */ + val topHeadsUpHeight: Int + /** * Since this is an interface rather than a literal View, this provides cast-like access to the * underlying view. @@ -72,9 +75,18 @@ interface NotificationScrollView { /** Sets whether the view is displayed in doze mode. */ fun setDozing(dozing: Boolean) - /** Sets a listener to be notified, when the stack height might have changed. */ + /** Adds a listener to be notified, when the stack height might have changed. */ fun addStackHeightChangedListener(runnable: Runnable) /** @see addStackHeightChangedListener */ fun removeStackHeightChangedListener(runnable: Runnable) + + /** + * Adds a listener to be notified, when the height of the top heads up notification might have + * changed. + */ + fun addHeadsUpHeightChangedListener(runnable: Runnable) + + /** @see addHeadsUpHeightChangedListener */ + fun removeHeadsUpHeightChangedListener(runnable: Runnable) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 622d8e7b2307..fd08e898fce3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -80,7 +80,6 @@ constructor( launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } - launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } } launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } } launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } } launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } } @@ -88,11 +87,9 @@ constructor( launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) - view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) - view.setHeadsUpHeightConsumer(null) } } } 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..e90a64a32fba 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. @@ -136,8 +139,6 @@ constructor( */ val scrolledToTop: Flow<Boolean> = stackAppearanceInteractor.scrolledToTop.dumpValue("scrolledToTop") - /** The y-coordinate in px of bottom of the contents of the HUN. */ - val headsUpTop: Flow<Float> = stackAppearanceInteractor.headsUpTop.dumpValue("headsUpTop") /** Receives the amount (px) that the stack should scroll due to internal expansion. */ val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll @@ -147,12 +148,12 @@ constructor( */ val currentGestureOverscrollConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureOverscroll - /** Receives the height of the heads up notification. */ - val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight /** 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 +163,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/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 97b86e3371f5..634bd7e4cd41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -29,7 +29,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow /** * ViewModel used by the Notification placeholders inside the scene container to update the @@ -61,10 +60,6 @@ constructor( interactor.setConstrainedAvailableSpace(height) } - fun onHeadsUpTopChanged(headsUpTop: Float) { - interactor.setHeadsUpTop(headsUpTop) - } - /** Sets the content alpha for the current state of the brightness mirror */ fun setAlphaForBrightnessMirror(alpha: Float) { interactor.setAlphaForBrightnessMirror(alpha) @@ -74,9 +69,6 @@ constructor( val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding") - /** The height in px of the contents of the HUN. */ - val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight") - /** * The amount [0-1] that the shade or quick settings has been opened. At 0, the shade is closed; * at 1, either the shade or quick settings is open. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 6546db9a2868..2ab7aa95ff56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -42,7 +42,10 @@ constructor( private val activityStarterInternal: ActivityStarterInternal = legacyActivityStarter.get() override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) { - activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent) + activityStarterInternal.startPendingIntentDismissingKeyguard( + intent = intent, + dismissShade = true + ) } override fun startPendingIntentDismissingKeyguard( @@ -52,6 +55,7 @@ constructor( activityStarterInternal.startPendingIntentDismissingKeyguard( intent = intent, intentSentUiThreadCallback = intentSentUiThreadCallback, + dismissShade = true, ) } @@ -64,6 +68,7 @@ constructor( intent = intent, intentSentUiThreadCallback = intentSentUiThreadCallback, associatedView = associatedView, + dismissShade = true, ) } @@ -76,6 +81,7 @@ constructor( intent = intent, intentSentUiThreadCallback = intentSentUiThreadCallback, animationController = animationController, + dismissShade = true, ) } @@ -89,11 +95,13 @@ constructor( intentSentUiThreadCallback = intentSentUiThreadCallback, animationController = animationController, showOverLockscreen = true, + dismissShade = true, ) } override fun startPendingIntentMaybeDismissingKeyguard( intent: PendingIntent, + dismissShade: Boolean, intentSentUiThreadCallback: Runnable?, animationController: ActivityTransitionAnimator.Controller?, fillInIntent: Intent?, @@ -104,6 +112,7 @@ constructor( intentSentUiThreadCallback = intentSentUiThreadCallback, animationController = animationController, showOverLockscreen = true, + dismissShade = dismissShade, fillInIntent = fillInIntent, extraOptions = extraOptions, ) @@ -179,6 +188,7 @@ constructor( showOverLockscreenWhenLocked = showOverLockscreenWhenLocked, ) } + override fun startActivity( intent: Intent, dismissShade: Boolean, @@ -199,6 +209,7 @@ constructor( postOnUiThread { activityStarterInternal.startPendingIntentDismissingKeyguard( intent = intent, + dismissShade = true, ) } } @@ -211,6 +222,7 @@ constructor( activityStarterInternal.startPendingIntentDismissingKeyguard( intent = intent, animationController = animationController, + dismissShade = true, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt index e8443982d560..c9becb4289b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt @@ -34,6 +34,7 @@ interface ActivityStarterInternal { */ fun startPendingIntentDismissingKeyguard( intent: PendingIntent, + dismissShade: Boolean, intentSentUiThreadCallback: Runnable? = null, associatedView: View? = null, animationController: ActivityTransitionAnimator.Controller? = null, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt index c101755bcf38..e580f6458be8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt @@ -35,6 +35,7 @@ import javax.inject.Inject class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInternal { override fun startPendingIntentDismissingKeyguard( intent: PendingIntent, + dismissShade: Boolean, intentSentUiThreadCallback: Runnable?, associatedView: View?, animationController: ActivityTransitionAnimator.Controller?, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 8fb552f167bc..05a43917f7e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -199,6 +199,11 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner, CoreStartable boolean isLaunchingActivityOverLockscreen(); + /** + * Whether an activity launch over lockscreen is causing the shade to be dismissed. + */ + boolean isDismissingShadeForActivityLaunch(); + void onKeyguardViewManagerStatesUpdated(); /** */ @@ -322,6 +327,11 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner, CoreStartable @Deprecated float getDisplayDensity(); + /** + * Forwards touch events to communal hub + */ + void handleCommunalHubTouch(MotionEvent event); + public static class KeyboardShortcutsMessage { final int mDeviceId; @@ -333,7 +343,8 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner, CoreStartable /** * Sets launching activity over LS state in central surfaces. */ - void setIsLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen); + void setIsLaunchingActivityOverLockscreen( + boolean isLaunchingActivityOverLockscreen, boolean dismissShade); /** * Gets an animation controller from a notification row. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt index 8af7ee8389e5..a7b54847cdf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -39,6 +39,7 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun updateIsKeyguard(forceStateChange: Boolean) = false override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null override fun isLaunchingActivityOverLockscreen() = false + override fun isDismissingShadeForActivityLaunch() = false override fun onKeyguardViewManagerStatesUpdated() {} override fun getCommandQueuePanelsEnabled() = false override fun showWirelessChargingAnimation(batteryLevel: Int) {} @@ -80,6 +81,7 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun shouldIgnoreTouch() = false override fun isDeviceInteractive() = false override fun handleDreamTouch(event: MotionEvent?) {} + override fun handleCommunalHubTouch(event: MotionEvent?) {} override fun awakenDreams() {} override fun isBouncerShowing() = false override fun isBouncerShowingScrimmed() = false @@ -96,7 +98,10 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {} override fun getQSPanelController(): QSPanelController? = null override fun getDisplayDensity() = 0f - override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {} + override fun setIsLaunchingActivityOverLockscreen( + isLaunchingActivityOverLockscreen: Boolean, + dismissShade: Boolean, + ) {} override fun getAnimatorControllerFromNotification( associatedView: ExpandableNotificationRow?, ): ActivityTransitionAnimator.Controller? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index d3d2b1ebcb88..78a803618c8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -172,6 +172,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.CameraLauncher; +import com.android.systemui.shade.GlanceableHubContainerController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.QuickSettingsController; @@ -544,6 +545,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Fingerprint (as computed by getLoggingFingerprint() of the last logged state. private int mLastLoggedStateFingerprint; private boolean mIsLaunchingActivityOverLockscreen; + private boolean mDismissingShadeForActivityLaunch; private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); protected final BatteryController mBatteryController; @@ -594,6 +596,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener = (extractor, which) -> updateTheme(); private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor; + private final GlanceableHubContainerController mGlanceableHubContainerController; /** * Public constructor for CentralSurfaces. @@ -706,7 +709,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { UserTracker userTracker, Provider<FingerprintManager> fingerprintManager, ActivityStarter activityStarter, - BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor + BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor, + GlanceableHubContainerController glanceableHubContainerController ) { mContext = context; mNotificationsController = notificationsController; @@ -801,6 +805,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mFingerprintManager = fingerprintManager; mActivityStarter = activityStarter; mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor; + mGlanceableHubContainerController = glanceableHubContainerController; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -1575,6 +1580,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mIsLaunchingActivityOverLockscreen; } + @Override + public boolean isDismissingShadeForActivityLaunch() { + return mDismissingShadeForActivityLaunch; + } + /** * To be called when there's a state change in StatusBarKeyguardViewManager. */ @@ -2945,6 +2955,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override + public void handleCommunalHubTouch(MotionEvent event) { + mGlanceableHubContainerController.onTouchEvent(event); + } + + @Override public void awakenDreams() { mUiBgExecutor.execute(() -> { try { @@ -3306,8 +3321,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public void setIsLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) { + public void setIsLaunchingActivityOverLockscreen( + boolean isLaunchingActivityOverLockscreen, boolean dismissShade) { mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen; + mDismissingShadeForActivityLaunch = dismissShade; mKeyguardViewMediator.launchingActivityOverLockscreen(mIsLaunchingActivityOverLockscreen); } 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/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 3063aed3f5e0..77f37063809c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -54,13 +54,13 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.util.Assert; +import com.android.systemui.util.CopyOnLoopListenerSet; +import com.android.systemui.util.IListenerSet; import dagger.Lazy; import kotlinx.coroutines.ExperimentalCoroutinesApi; -import java.util.ArrayList; - import javax.inject.Inject; /** @@ -69,7 +69,7 @@ import javax.inject.Inject; @ExperimentalCoroutinesApi @SysUISingleton public final class DozeServiceHost implements DozeHost { private static final String TAG = "DozeServiceHost"; - private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + private final IListenerSet<Callback> mCallbacks = new CopyOnLoopListenerSet<>(); private final DozeLog mDozeLog; private final PowerManager mPowerManager; private boolean mAnimateWakeup; @@ -178,8 +178,8 @@ public final class DozeServiceHost implements DozeHost { */ public void fireSideFpsAcquisitionStarted() { Assert.isMainThread(); - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onSideFingerprintAcquisitionStarted(); + for (Callback callback : mCallbacks) { + callback.onSideFingerprintAcquisitionStarted(); } } @@ -211,7 +211,7 @@ public final class DozeServiceHost implements DozeHost { @Override public void addCallback(@NonNull Callback callback) { Assert.isMainThread(); - mCallbacks.add(callback); + mCallbacks.addIfAbsent(callback); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 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..4bf122dd3b6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -86,6 +86,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>(); private final VisualStabilityProvider mVisualStabilityProvider; + private final AvalancheController mAvalancheController; + // TODO(b/328393698) move the topHeadsUpRow logic to an interactor private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow = StateFlowKt.MutableStateFlow(null); @@ -155,6 +157,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements mBypassController = bypassController; mGroupMembershipManager = groupMembershipManager; mVisualStabilityProvider = visualStabilityProvider; + mAvalancheController = avalancheController; updateResources(); configurationController.addCallback(new ConfigurationController.ConfigurationListener() { @@ -249,7 +252,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 +384,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 +575,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements } else if (mTrackingHeadsUp) { mEntriesToRemoveAfterExpand.add(entry); } else { - removeEntry(entry.getKey()); + removeEntry(entry.getKey(), "createRemoveRunnable"); } }; } @@ -653,15 +656,16 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD; boolean isKeyguard = newState == StatusBarState.KEYGUARD; mStatusBarState = newState; + if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) { ArrayList<String> keysToRemove = new ArrayList<>(); - for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) { + for (HeadsUpEntry entry : getHeadsUpEntryList()) { if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) { keysToRemove.add(entry.mEntry.getKey()); } } for (String key : keysToRemove) { - removeEntry(key); + removeEntry(key, "mStatusBarStateListener"); } } } @@ -671,7 +675,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements if (!isDozing) { // Let's make sure all huns we got while dozing time out within the normal timeout // duration. Otherwise they could get stuck for a very long time - for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) { + for (HeadsUpEntry entry : getHeadsUpEntryList()) { entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)"); } } 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/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index bcc7db162ddd..b448d85bfd49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -33,10 +33,13 @@ import android.view.View import android.view.WindowManager import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper +import com.android.systemui.Flags.communalHub import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.DelegateTransitionAnimatorController import com.android.systemui.assist.AssistManager import com.android.systemui.camera.CameraIntents +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.dagger.qualifiers.Main @@ -89,6 +92,7 @@ constructor( private val userTracker: UserTracker, private val activityIntentHelper: ActivityIntentHelper, @Main private val mainExecutor: DelayableExecutor, + private val communalSceneInteractor: CommunalSceneInteractor, ) : ActivityStarterInternal { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() @@ -219,6 +223,7 @@ constructor( override fun startPendingIntentDismissingKeyguard( intent: PendingIntent, + dismissShade: Boolean, intentSentUiThreadCallback: Runnable?, associatedView: View?, animationController: ActivityTransitionAnimator.Controller?, @@ -257,12 +262,12 @@ constructor( val statusBarController = wrapAnimationControllerForShadeOrStatusBar( animationController = animationController, - dismissShade = true, + dismissShade = dismissShade, isLaunchForActivity = intent.isActivity, ) val controller = if (actuallyShowOverLockscreen) { - wrapAnimationControllerForLockscreen(statusBarController) + wrapAnimationControllerForLockscreen(dismissShade, statusBarController) } else { statusBarController } @@ -270,7 +275,7 @@ constructor( // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we // run the animation on the keyguard). The animation will take care of (instantly) // collapsing the shade and hiding the keyguard once it is done. - val collapse = !animate + val collapse = dismissShade && !animate val runnable = Runnable { try { activityTransitionAnimator.startPendingIntentWithAnimation( @@ -377,7 +382,7 @@ constructor( dismissShade = dismissShade, isLaunchForActivity = true, ) - controller = wrapAnimationControllerForLockscreen(delegate) + controller = wrapAnimationControllerForLockscreen(dismissShade, delegate) } else if (dismissShade) { // The animation will take care of dismissing the shade at the end of the animation. // If we don't animate, collapse it directly. @@ -462,6 +467,9 @@ constructor( if (dismissShade) { shadeControllerLazy.get().collapseShadeForActivityStart() } + if (communalHub()) { + communalSceneInteractor.snapToScene(CommunalScenes.Blank) + } return deferred } @@ -532,6 +540,7 @@ constructor( * lockscreen, the correct flags are set for it to be occluded. */ private fun wrapAnimationControllerForLockscreen( + dismissShade: Boolean, animationController: ActivityTransitionAnimator.Controller? ): ActivityTransitionAnimator.Controller? { return animationController?.let { @@ -539,7 +548,7 @@ constructor( override fun onIntentStarted(willAnimate: Boolean) { delegate.onIntentStarted(willAnimate) if (willAnimate) { - centralSurfaces?.setIsLaunchingActivityOverLockscreen(true) + centralSurfaces?.setIsLaunchingActivityOverLockscreen(true, dismissShade) } } @@ -570,7 +579,10 @@ constructor( // mIsLaunchingActivityOverLockscreen being true means that we will // collapse the shade (or at least run the post collapse runnables) // later on. - centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) + centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false) + if (communalHub()) { + communalSceneInteractor.snapToScene(CommunalScenes.Blank) + } delegate.onTransitionAnimationEnd(isExpandingFullyAbove) } @@ -586,7 +598,7 @@ constructor( // mIsLaunchingActivityOverLockscreen being true means that we will // collapse the shade (or at least run the // post collapse // runnables) later on. - centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) + centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false) delegate.onTransitionAnimationCancelled(newKeyguardOccludedState) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 5bbc3bd92543..ebb62ec7bcac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -1071,12 +1071,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED); if (mCentralSurfaces.isLaunchingActivityOverLockscreen()) { - // When isLaunchingActivityOverLockscreen() is true, we know for sure that the post - // collapse runnables will be run. - mShadeController.get().addPostCollapseAction(() -> { + final Runnable postCollapseAction = () -> { mNotificationShadeWindowController.setKeyguardOccluded(isOccluded); reset(true /* hideBouncerWhenShowing */); - }); + }; + if (mCentralSurfaces.isDismissingShadeForActivityLaunch()) { + // When isDismissingShadeForActivityLaunch() is true, we know for sure that the + // post collapse runnables will be run. + mShadeController.get().addPostCollapseAction(postCollapseAction); + } else { + postCollapseAction.run(); + } return; } } else if (isShowing && isUnOccluding) { 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/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 97f9e066ded5..aac211ae6b46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -39,6 +39,7 @@ import com.android.app.animation.Interpolators; import com.android.app.animation.InterpolatorsAndroidX; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; +import com.android.systemui.Flags; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; @@ -51,7 +52,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameView; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; @@ -135,8 +135,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger; private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory; private final OngoingCallController mOngoingCallController; - // TODO(b/332662551): Use this view model to show the ongoing activity chips. - private final OngoingActivityChipsViewModel mOngoingActivityChipsViewModel; private final SystemStatusAnimationScheduler mAnimationScheduler; private final StatusBarLocationPublisher mLocationPublisher; private final NotificationIconAreaController mNotificationIconAreaController; @@ -207,6 +205,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private boolean mTransitionFromLockscreenToDreamStarted = false; /** + * True if there's an active ongoing activity that should be showing a chip and false otherwise. + */ + private boolean mHasOngoingActivity; + + /** * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives * a new status bar window state. */ @@ -216,11 +219,12 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue }; private DisposableHandle mNicBindingDisposable; + private boolean mAnimationsEnabled = true; + @Inject public CollapsedStatusBarFragment( StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory, OngoingCallController ongoingCallController, - OngoingActivityChipsViewModel ongoingActivityChipsViewModel, SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, NotificationIconAreaController notificationIconAreaController, @@ -246,7 +250,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue DemoModeController demoModeController) { mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory; mOngoingCallController = ongoingCallController; - mOngoingActivityChipsViewModel = ongoingActivityChipsViewModel; mAnimationScheduler = animationScheduler; mLocationPublisher = locationPublisher; mNotificationIconAreaController = notificationIconAreaController; @@ -401,6 +404,17 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return mBlockedIcons; } + + @VisibleForTesting + void enableAnimationsForTesting() { + mAnimationsEnabled = true; + } + + @VisibleForTesting + void disableAnimationsForTesting() { + mAnimationsEnabled = false; + } + @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -476,7 +490,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue notificationIconArea.addView(mNotificationIconAreaInner); } - updateNotificationIconAreaAndCallChip(/* animate= */ false); + updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false); Trace.endSection(); } @@ -493,15 +507,21 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private StatusBarVisibilityChangeListener mStatusBarVisibilityChangeListener = new StatusBarVisibilityChangeListener() { - @Override - public void onStatusBarVisibilityMaybeChanged() { - updateStatusBarVisibilities(/* animate= */ true); - } + @Override + public void onStatusBarVisibilityMaybeChanged() { + updateStatusBarVisibilities(/* animate= */ true); + } - @Override - public void onTransitionFromLockscreenToDreamStarted() { - mTransitionFromLockscreenToDreamStarted = true; - } + @Override + public void onTransitionFromLockscreenToDreamStarted() { + mTransitionFromLockscreenToDreamStarted = true; + } + + @Override + public void onOngoingActivityStatusChanged(boolean hasOngoingActivity) { + mHasOngoingActivity = hasOngoingActivity; + updateStatusBarVisibilities(/* animate= */ true); + } }; @Override @@ -532,11 +552,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } } - // The ongoing call chip and notification icon visibilities are intertwined, so update both - // if either change. - if (newModel.getShowNotificationIcons() != previousModel.getShowNotificationIcons() - || newModel.getShowOngoingCallChip() != previousModel.getShowOngoingCallChip()) { - updateNotificationIconAreaAndCallChip(animate); + // The ongoing activity chip and notification icon visibilities are intertwined, so update + // both if either change. + boolean notifsChanged = + newModel.getShowNotificationIcons() != previousModel.getShowNotificationIcons(); + boolean ongoingActivityChanged = + newModel.getShowOngoingActivityChip() != previousModel.getShowOngoingActivityChip(); + if (notifsChanged || ongoingActivityChanged) { + updateNotificationIconAreaAndOngoingActivityChip(animate); } // The clock may have already been hidden, but we might want to shift its @@ -566,45 +589,58 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return new StatusBarVisibilityModel( /* showClock= */ false, /* showNotificationIcons= */ false, - /* showOngoingCallChip= */ false, + /* showOngoingActivityChip= */ false, /* showSystemInfo= */ false); } boolean showClock = externalModel.getShowClock() && !headsUpVisible; - boolean showOngoingCallChip = mOngoingCallController.hasOngoingCall() && !headsUpVisible; + + boolean showOngoingActivityChip; + if (Flags.statusBarScreenSharingChips()) { + // If this flag is on, the ongoing activity status comes from + // CollapsedStatusBarViewBinder, which updates the mHasOngoingActivity variable. + showOngoingActivityChip = mHasOngoingActivity; + } else { + // If this flag is off, the only ongoing activity is the ongoing call, and we pull it + // from the controller directly. + showOngoingActivityChip = mOngoingCallController.hasOngoingCall(); + } + return new StatusBarVisibilityModel( showClock, externalModel.getShowNotificationIcons(), - showOngoingCallChip, + showOngoingActivityChip && !headsUpVisible, externalModel.getShowSystemInfo()); } /** - * Updates the visibility of the notification icon area and ongoing call chip based on disabled1 - * state. + * Updates the visibility of the notification icon area and ongoing activity chip based on + * mLastModifiedVisibility. */ - private void updateNotificationIconAreaAndCallChip(boolean animate) { + private void updateNotificationIconAreaAndOngoingActivityChip(boolean animate) { StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility; boolean disableNotifications = !visibilityModel.getShowNotificationIcons(); - boolean hasOngoingCall = visibilityModel.getShowOngoingCallChip(); + boolean hasOngoingActivity = visibilityModel.getShowOngoingActivityChip(); - // Hide notifications if the disable flag is set or we have an ongoing call. - if (disableNotifications || hasOngoingCall) { - hideNotificationIconArea(animate && !hasOngoingCall); + // Hide notifications if the disable flag is set or we have an ongoing activity. + if (disableNotifications || hasOngoingActivity) { + hideNotificationIconArea(animate && !hasOngoingActivity); } else { showNotificationIconArea(animate); } - // Show the ongoing call chip only if there is an ongoing call *and* notification icons - // are allowed. (The ongoing call chip occupies the same area as the notification icons, - // so if the icons are disabled then the call chip should be, too.) - boolean showOngoingCallChip = hasOngoingCall && !disableNotifications; - if (showOngoingCallChip) { + // Show the ongoing activity chip only if there is an ongoing activity *and* notification + // icons are allowed. (The ongoing activity chip occupies the same area as the notification, + // icons so if the icons are disabled then the activity chip should be, too.) + boolean showOngoingActivityChip = hasOngoingActivity && !disableNotifications; + if (showOngoingActivityChip) { showOngoingActivityChip(animate); } else { hideOngoingActivityChip(animate); } - mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip); + if (!Flags.statusBarScreenSharingChips()) { + mOngoingCallController.notifyChipVisibilityChanged(showOngoingActivityChip); + } } private boolean shouldHideStatusBar() { @@ -702,8 +738,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue /** * Displays the ongoing activity chip. * - * For now, this chip will only ever contain the ongoing call information, but after b/332662551 - * feature is implemented, it will support different kinds of ongoing activities. + * If Flags.statusBarScreenSharingChips is disabled, this chip will only ever contain the + * ongoing call information, If that flag is enabled, it will support different kinds of ongoing + * activities. See b/332662551. */ private void showOngoingActivityChip(boolean animate) { animateShow(mOngoingActivityChip, animate); @@ -746,7 +783,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue */ private void animateHiddenState(final View v, int state, boolean animate) { v.animate().cancel(); - if (!animate) { + if (!animate || !mAnimationsEnabled) { v.setAlpha(0f); v.setVisibility(state); return; @@ -773,7 +810,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private void animateShow(View v, boolean animate) { v.animate().cancel(); v.setVisibility(View.VISIBLE); - if (!animate) { + if (!animate || !mAnimationsEnabled) { v.setAlpha(1f); return; } @@ -872,6 +909,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void dump(PrintWriter printWriter, String[] args) { IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, /* singleIndent= */" "); + pw.println("mHasOngoingActivity=" + mHasOngoingActivity); + pw.println("mAnimationsEnabled=" + mAnimationsEnabled); StatusBarFragmentComponent component = mStatusBarFragmentComponent; if (component == null) { pw.println("StatusBarFragmentComponent is null"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt index 7cdb9c0a7aa8..0a19023d9e8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt @@ -59,13 +59,13 @@ class CollapsedStatusBarFragmentLogger @Inject constructor( { bool1 = model.showClock bool2 = model.showNotificationIcons - bool3 = model.showOngoingCallChip + bool3 = model.showOngoingActivityChip bool4 = model.showSystemInfo }, { "New visibilities calculated internally. " + "showClock=$bool1 " + "showNotificationIcons=$bool2 " + - "showOngoingCallChip=$bool3 " + + "showOngoingActivityChip=$bool3 " + "showSystemInfo=$bool4" } ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt index cf54cb7aa954..fe24faece1d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt @@ -26,7 +26,7 @@ import android.app.StatusBarManager.DISABLE_SYSTEM_INFO data class StatusBarVisibilityModel( val showClock: Boolean, val showNotificationIcons: Boolean, - val showOngoingCallChip: Boolean, + val showOngoingActivityChip: Boolean, val showSystemInfo: Boolean, ) { companion object { @@ -48,7 +48,7 @@ data class StatusBarVisibilityModel( showNotificationIcons = (disabled1 and DISABLE_NOTIFICATION_ICONS) == 0, // TODO(b/279899176): [CollapsedStatusBarFragment] always overwrites this with the // value of [OngoingCallController]. Do we need to process the flag here? - showOngoingCallChip = (disabled1 and DISABLE_ONGOING_CALL_CHIP) == 0, + showOngoingActivityChip = (disabled1 and DISABLE_ONGOING_CALL_CHIP) == 0, showSystemInfo = (disabled1 and DISABLE_SYSTEM_INFO) == 0 && (disabled2 and DISABLE2_SYSTEM_ICONS) == 0 diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/DeviceBasedSatelliteInputLog.kt index b35b7f5debb3..73c015d234f5 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/DeviceBasedSatelliteInputLog.kt @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.dreams.homecontrols -import android.app.Activity -import android.service.dreams.DreamService +package com.android.systemui.statusbar.pipeline.dagger -fun interface DreamActivityProvider { - /** - * Provides abstraction for getting the activity associated with a dream service, so that the - * activity can be mocked in tests. - */ - fun getActivity(dreamService: DreamService): Activity? -} +import javax.inject.Qualifier + +/** + * Logs for device-based satellite events that are **not** that frequent/chatty. + * + * For chatty logs, use [VerboseDeviceBasedSatelliteInputLog] instead. + */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class DeviceBasedSatelliteInputLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 88ca9e5f1744..a81bfa4a8c0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -227,9 +227,16 @@ abstract class StatusBarPipelineModule { @Provides @SysUISingleton - @OemSatelliteInputLog - fun provideOemSatelliteInputLog(factory: LogBufferFactory): LogBuffer { - return factory.create("DeviceBasedSatelliteInputLog", 150) + @DeviceBasedSatelliteInputLog + fun provideDeviceBasedSatelliteInputLog(factory: LogBufferFactory): LogBuffer { + return factory.create("DeviceBasedSatelliteInputLog", 200) + } + + @Provides + @SysUISingleton + @VerboseDeviceBasedSatelliteInputLog + fun provideVerboseDeviceBasedSatelliteInputLog(factory: LogBufferFactory): LogBuffer { + return factory.create("VerboseDeviceBasedSatelliteInputLog", 200) } const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseDeviceBasedSatelliteInputLog.kt index 252945f1ed6a..af6805513c89 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseDeviceBasedSatelliteInputLog.kt @@ -16,11 +16,14 @@ package com.android.systemui.statusbar.pipeline.dagger -import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository import javax.inject.Qualifier -/** Detailed [DeviceBasedSatelliteRepository] logs */ +/** + * Logs for device-based satellite events that are frequent/chatty. + * + * For non-chatty logs, use [DeviceBasedSatelliteInputLog] instead. + */ @Qualifier @MustBeDocumented -@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) -annotation class OemSatelliteInputLog +@Retention(AnnotationRetention.RUNTIME) +annotation class VerboseDeviceBasedSatelliteInputLog 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..43258972ea34 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 @@ -30,7 +32,8 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.core.MessageInitializer import com.android.systemui.log.core.MessagePrinter -import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog +import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog +import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported @@ -38,6 +41,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 +55,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 +99,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,9 +145,11 @@ class DeviceBasedSatelliteRepositoryImpl @Inject constructor( satelliteManagerOpt: Optional<SatelliteManager>, + telephonyManager: TelephonyManager, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, - @OemSatelliteInputLog private val logBuffer: LogBuffer, + @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer, + @VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer, private val systemClock: SystemClock, ) : RealDeviceBasedSatelliteRepository { @@ -201,11 +216,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,14 +301,18 @@ 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 private fun signalStrengthFlow(sm: SupportedSatelliteManager) = conflatedCallbackFlow { val cb = NtnSignalStrengthCallback { signalStrength -> - logBuffer.i({ int1 = signalStrength.level }) { + verboseLogBuffer.i({ int1 = signalStrength.level }) { "onNtnSignalStrengthChanged: level=$int1" } trySend(signalStrength.level) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt index 5b954b272044..b66ace6b0fe3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt @@ -21,7 +21,7 @@ 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.statusbar.pipeline.dagger.OemSatelliteInputLog +import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState @@ -48,7 +48,7 @@ constructor( deviceProvisioningInteractor: DeviceProvisioningInteractor, wifiInteractor: WifiInteractor, @Application scope: CoroutineScope, - @OemSatelliteInputLog private val logBuffer: LogBuffer, + @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer, ) { /** Must be observed by any UI showing Satellite iconography */ val isSatelliteAllowed = 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..0ed1b9b0f77a 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,12 +18,13 @@ 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 import com.android.systemui.res.R import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository -import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog +import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel @@ -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( @@ -67,7 +70,7 @@ constructor( interactor: DeviceBasedSatelliteInteractor, @Application scope: CoroutineScope, airplaneModeRepository: AirplaneModeRepository, - @OemSatelliteInputLog logBuffer: LogBuffer, + @DeviceBasedSatelliteInputLog logBuffer: LogBuffer, ) : DeviceBasedSatelliteViewModel { private val shouldShowIcon: Flow<Boolean> = interactor.areAllConnectionsOutOfService.flatMapLatest { allOos -> @@ -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/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index 7d7f49bb8d17..a2ec1f21a35c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -19,11 +19,17 @@ package com.android.systemui.statusbar.pipeline.shared.ui.binder import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.view.View +import android.widget.ImageView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.Flags +import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R +import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.binder.ChipChronometerBinder +import com.android.systemui.statusbar.chips.ui.view.ChipChronometer import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel import javax.inject.Inject @@ -75,6 +81,35 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } } + + if (Flags.statusBarScreenSharingChips()) { + val chipView: View = view.requireViewById(R.id.ongoing_activity_chip) + val chipIconView: ImageView = + chipView.requireViewById(R.id.ongoing_activity_chip_icon) + val chipTimeView: ChipChronometer = + chipView.requireViewById(R.id.ongoing_activity_chip_time) + launch { + viewModel.ongoingActivityChip.collect { chipModel -> + when (chipModel) { + is OngoingActivityChipModel.Shown -> { + IconViewBinder.bind(chipModel.icon, chipIconView) + ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView) + // TODO(b/332662551): Attach click listener to chip + + listener.onOngoingActivityStatusChanged( + hasOngoingActivity = true + ) + } + is OngoingActivityChipModel.Hidden -> { + chipTimeView.stop() + listener.onOngoingActivityStatusChanged( + hasOngoingActivity = false + ) + } + } + } + } + } } } } @@ -120,4 +155,7 @@ interface StatusBarVisibilityChangeListener { /** Called when a transition from lockscreen to dream has started. */ fun onTransitionFromLockscreenToDreamStarted() + + /** Called when the status of the ongoing activity chip (active or not active) has changed. */ + fun onOngoingActivityStatusChanged(hasOngoingActivity: Boolean) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt index 0a6e95eee127..bb3a67ed49bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -24,6 +24,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor @@ -59,6 +61,9 @@ interface CollapsedStatusBarViewModel { /** Emits whenever a transition from lockscreen to dream has started. */ val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> + /** The ongoing activity chip that should be shown on the left-hand side of the status bar. */ + val ongoingActivityChip: StateFlow<OngoingActivityChipModel> + /** * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where * status bar and navigation icons dim. In this mode, a notification dot appears where the @@ -78,6 +83,7 @@ constructor( private val lightsOutInteractor: LightsOutInteractor, private val notificationsInteractor: ActiveNotificationsInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, + ongoingActivityChipsViewModel: OngoingActivityChipsViewModel, @Application coroutineScope: CoroutineScope, ) : CollapsedStatusBarViewModel { override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> = @@ -91,6 +97,8 @@ constructor( .filter { it.transitionState == TransitionState.STARTED } .map {} + override val ongoingActivityChip = ongoingActivityChipsViewModel.chip + override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) { emptyFlow() 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..a97298527e11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.policy import android.util.Log import androidx.annotation.VisibleForTesting +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager @@ -35,6 +37,7 @@ class AvalancheController @Inject constructor( dumpManager: DumpManager, + private val uiEventLogger: UiEventLogger ) : Dumpable { private val tag = "AvalancheController" @@ -65,6 +68,21 @@ constructor( // For debugging only @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet() + enum class ThrottleEvent(private val id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "HUN was shown.") + SHOWN(1812), + + @UiEvent(doc = "HUN was dropped to show higher priority HUNs.") + DROPPED(1813), + + @UiEvent(doc = "HUN was removed while waiting to show.") + REMOVED(1814); + + override fun getId(): Int { + return id + } + } + init { dumpManager.registerNormalDumpable(tag, /* module */ this) } @@ -79,7 +97,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 +107,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 +152,23 @@ 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) + uiEventLogger.log(ThrottleEvent.REMOVED) } 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 +177,7 @@ constructor( showNext() runnable.run() } else { - log { "$fn => [removing untracked ${getKey(entry)}]" } + log { "$fn => removing untracked ${getKey(entry)}" } } logState("after $fn") } @@ -239,6 +261,25 @@ 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 + } + + fun getWaitingEntryList(): List<HeadsUpEntry> { + if (!NotificationThrottleHun.isEnabled) { + return mutableListOf() + } + return nextMap.keys.toList() + } + private fun isShowing(entry: HeadsUpEntry): Boolean { return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key } @@ -246,6 +287,7 @@ constructor( private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) { log { "SHOW: " + getKey(entry) } + uiEventLogger.log(ThrottleEvent.SHOWN) headsUpEntryShowing = entry runnableList.forEach { @@ -273,6 +315,12 @@ constructor( // Remove runnable labels for dropped huns val listToDrop = nextList.subList(1, nextList.size) + + // Log dropped HUNs + for (e in listToDrop) { + uiEventLogger.log(ThrottleEvent.DROPPED) + } + if (debug) { // Clear runnable labels for (e in listToDrop) { 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..4bd868179faf 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; + } + if (canRemoveImmediately(key)) { + removeEntry(key, "removeNotification (canRemoveImmediately)"); + return true; } - 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)"); } } @@ -275,7 +279,6 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * Returns the entry if it is managed by this manager. * @param key key of notification * @return the entry - * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries */ @Nullable public NotificationEntry getEntry(@NonNull String key) { @@ -290,8 +293,13 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @NonNull @Override public Stream<NotificationEntry> getAllEntries() { - // TODO(b/315362456) See if callers need to check AvalancheController - return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry); + return getHeadsUpEntryList().stream().map(headsUpEntry -> headsUpEntry.mEntry); + } + + public List<HeadsUpEntry> getHeadsUpEntryList() { + List<HeadsUpEntry> entryList = new ArrayList<>(mHeadsUpEntryMap.values()); + entryList.addAll(mAvalancheController.getWaitingEntryList()); + return entryList; } /** @@ -300,7 +308,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ @Override public boolean hasNotifications() { - return !mHeadsUpEntryMap.isEmpty(); + return !mHeadsUpEntryMap.isEmpty() + || !mAvalancheController.getWaitingEntryList().isEmpty(); } /** @@ -338,8 +347,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 +386,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 +397,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 +422,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 +441,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) { @@ -495,8 +512,10 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @Nullable protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) { - // TODO(b/315362456) See if callers need to check AvalancheController - return mHeadsUpEntryMap.get(key); + if (mHeadsUpEntryMap.containsKey(key)) { + return mHeadsUpEntryMap.get(key); + } + return mAvalancheController.getWaitingEntry(key); } /** @@ -584,7 +603,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"); @@ -676,8 +695,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ @Override public boolean isSticky(String key) { - // TODO(b/315362456) See if callers need to check AvalancheController - HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); if (headsUpEntry != null) { return headsUpEntry.isSticky(); } @@ -985,7 +1003,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/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 58b82f166623..da928a364984 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -32,6 +32,7 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import com.android.internal.camera.flags.Flags; +import com.android.systemui.util.ListenerSet; import java.util.Set; @@ -43,7 +44,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray(); private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray(); private Boolean mRequiresAuthentication; - private final Set<Callback> mCallbacks = new ArraySet<>(); + private final ListenerSet<Callback> mCallbacks = new ListenerSet<>(); public IndividualSensorPrivacyControllerImpl( @NonNull SensorPrivacyManager sensorPrivacyManager) { @@ -115,7 +116,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr @Override public void addCallback(@NonNull Callback listener) { - mCallbacks.add(listener); + mCallbacks.addIfAbsent(listener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java deleted file mode 100644 index 2cad8442e3ba..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2019 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.util.concurrency; - -import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; - -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Process; - -import com.android.systemui.Flags; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.BroadcastRunning; -import com.android.systemui.dagger.qualifiers.LongRunning; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dagger.qualifiers.NotifInflation; - -import dagger.Module; -import dagger.Provides; - -import java.util.concurrent.Executor; - -import javax.inject.Named; - -/** - * Dagger Module for classes found within the concurrent package. - */ -@Module -public abstract class SysUIConcurrencyModule { - - // Slow BG executor can potentially affect UI if UI is waiting for an updated state from this - // thread - private static final Long BG_SLOW_DISPATCH_THRESHOLD = 1000L; - private static final Long BG_SLOW_DELIVERY_THRESHOLD = 1000L; - private static final Long LONG_SLOW_DISPATCH_THRESHOLD = 2500L; - private static final Long LONG_SLOW_DELIVERY_THRESHOLD = 2500L; - private static final Long BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L; - private static final Long BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L; - private static final Long NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L; - private static final Long NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L; - - /** Background Looper */ - @Provides - @SysUISingleton - @Background - public static Looper provideBgLooper() { - HandlerThread thread = new HandlerThread("SysUiBg", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - thread.getLooper().setSlowLogThresholdMs(BG_SLOW_DISPATCH_THRESHOLD, - BG_SLOW_DELIVERY_THRESHOLD); - return thread.getLooper(); - } - - /** BroadcastRunning Looper (for sending and receiving broadcasts) */ - @Provides - @SysUISingleton - @BroadcastRunning - public static Looper provideBroadcastRunningLooper() { - HandlerThread thread = new HandlerThread("BroadcastRunning", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - thread.getLooper().setSlowLogThresholdMs(BROADCAST_SLOW_DISPATCH_THRESHOLD, - BROADCAST_SLOW_DELIVERY_THRESHOLD); - return thread.getLooper(); - } - - /** Long running tasks Looper */ - @Provides - @SysUISingleton - @LongRunning - public static Looper provideLongRunningLooper() { - HandlerThread thread = new HandlerThread("SysUiLng", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - thread.getLooper().setSlowLogThresholdMs(LONG_SLOW_DISPATCH_THRESHOLD, - LONG_SLOW_DELIVERY_THRESHOLD); - return thread.getLooper(); - } - - /** Notification inflation Looper */ - @Provides - @SysUISingleton - @NotifInflation - public static Looper provideNotifInflationLooper(@Background Looper bgLooper) { - if (!Flags.dedicatedNotifInflationThread()) { - return bgLooper; - } - - final HandlerThread thread = new HandlerThread("NotifInflation", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - final Looper looper = thread.getLooper(); - looper.setSlowLogThresholdMs(NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD, - NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD); - return looper; - } - - /** - * Background Handler. - * - * Prefer the Background Executor when possible. - */ - @Provides - @Background - public static Handler provideBgHandler(@Background Looper bgLooper) { - return new Handler(bgLooper); - } - - /** - * Provide a BroadcastRunning Executor (for sending and receiving broadcasts). - */ - @Provides - @SysUISingleton - @BroadcastRunning - public static Executor provideBroadcastRunningExecutor(@BroadcastRunning Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Long running Executor. - */ - @Provides - @SysUISingleton - @LongRunning - public static Executor provideLongRunningExecutor(@LongRunning Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Long running Executor. - */ - @Provides - @SysUISingleton - @LongRunning - public static DelayableExecutor provideLongRunningDelayableExecutor( - @LongRunning Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Background-Thread Executor. - */ - @Provides - @SysUISingleton - @Background - public static Executor provideBackgroundExecutor(@Background Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Background-Thread Executor. - */ - @Provides - @SysUISingleton - @Background - public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) { - return new ExecutorImpl(looper); - } - - /** - * Provide a Background-Thread Executor. - */ - @Provides - @SysUISingleton - @Background - public static RepeatableExecutor provideBackgroundRepeatableExecutor( - @Background DelayableExecutor exec) { - return new RepeatableExecutorImpl(exec); - } - - /** - * Provide a Main-Thread Executor. - */ - @Provides - @SysUISingleton - @Main - public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) { - return new RepeatableExecutorImpl(exec); - } - - /** */ - @Provides - @Main - public static MessageRouter providesMainMessageRouter( - @Main DelayableExecutor executor) { - return new MessageRouterImpl(executor); - } - - /** */ - @Provides - @Background - public static MessageRouter providesBackgroundMessageRouter( - @Background DelayableExecutor executor) { - return new MessageRouterImpl(executor); - } - - /** */ - @Provides - @SysUISingleton - @Named(TIME_TICK_HANDLER_NAME) - public static Handler provideTimeTickHandler() { - HandlerThread thread = new HandlerThread("TimeTick"); - thread.start(); - return new Handler(thread.getLooper()); - } - - /** */ - @Provides - @SysUISingleton - @NotifInflation - public static Executor provideNotifInflationExecutor(@NotifInflation Looper looper) { - return new ExecutorImpl(looper); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt new file mode 100644 index 000000000000..83e3428bb95f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt @@ -0,0 +1,194 @@ +/* + * 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.util.concurrency + +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.os.Process +import com.android.systemui.Dependency +import com.android.systemui.Flags +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.BroadcastRunning +import com.android.systemui.dagger.qualifiers.LongRunning +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dagger.qualifiers.NotifInflation +import dagger.Module +import dagger.Provides +import java.util.concurrent.Executor +import javax.inject.Named + +/** Dagger Module for classes found within the concurrent package. */ +@Module +object SysUIConcurrencyModule { + // Slow BG executor can potentially affect UI if UI is waiting for an updated state from this + // thread + private const val BG_SLOW_DISPATCH_THRESHOLD = 1000L + private const val BG_SLOW_DELIVERY_THRESHOLD = 1000L + private const val LONG_SLOW_DISPATCH_THRESHOLD = 2500L + private const val LONG_SLOW_DELIVERY_THRESHOLD = 2500L + private const val BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L + private const val BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L + private const val NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L + private const val NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L + + /** Background Looper */ + @Provides + @SysUISingleton + @Background + fun provideBgLooper(): Looper { + val thread = HandlerThread("SysUiBg", Process.THREAD_PRIORITY_BACKGROUND) + thread.start() + thread + .getLooper() + .setSlowLogThresholdMs(BG_SLOW_DISPATCH_THRESHOLD, BG_SLOW_DELIVERY_THRESHOLD) + return thread.getLooper() + } + + /** BroadcastRunning Looper (for sending and receiving broadcasts) */ + @Provides + @SysUISingleton + @BroadcastRunning + fun provideBroadcastRunningLooper(): Looper { + val thread = HandlerThread("BroadcastRunning", Process.THREAD_PRIORITY_BACKGROUND) + thread.start() + thread + .getLooper() + .setSlowLogThresholdMs( + BROADCAST_SLOW_DISPATCH_THRESHOLD, + BROADCAST_SLOW_DELIVERY_THRESHOLD + ) + return thread.getLooper() + } + + /** Long running tasks Looper */ + @Provides + @SysUISingleton + @LongRunning + fun provideLongRunningLooper(): Looper { + val thread = HandlerThread("SysUiLng", Process.THREAD_PRIORITY_BACKGROUND) + thread.start() + thread + .getLooper() + .setSlowLogThresholdMs(LONG_SLOW_DISPATCH_THRESHOLD, LONG_SLOW_DELIVERY_THRESHOLD) + return thread.getLooper() + } + + /** Notification inflation Looper */ + @Provides + @SysUISingleton + @NotifInflation + fun provideNotifInflationLooper(@Background bgLooper: Looper): Looper { + if (!Flags.dedicatedNotifInflationThread()) { + return bgLooper + } + val thread = HandlerThread("NotifInflation", Process.THREAD_PRIORITY_BACKGROUND) + thread.start() + val looper = thread.getLooper() + looper.setSlowLogThresholdMs( + NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD, + NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD + ) + return looper + } + + /** + * Background Handler. + * + * Prefer the Background Executor when possible. + */ + @Provides + @Background + fun provideBgHandler(@Background bgLooper: Looper): Handler = Handler(bgLooper) + + /** Provide a BroadcastRunning Executor (for sending and receiving broadcasts). */ + @Provides + @SysUISingleton + @BroadcastRunning + fun provideBroadcastRunningExecutor(@BroadcastRunning looper: Looper): Executor = + ExecutorImpl(looper) + + /** Provide a Long running Executor. */ + @Provides + @SysUISingleton + @LongRunning + fun provideLongRunningExecutor(@LongRunning looper: Looper): Executor = ExecutorImpl(looper) + + /** Provide a Long running Executor. */ + @Provides + @SysUISingleton + @LongRunning + fun provideLongRunningDelayableExecutor(@LongRunning looper: Looper): DelayableExecutor = + ExecutorImpl(looper) + + /** Provide a Background-Thread Executor. */ + @Provides + @SysUISingleton + @Background + fun provideBackgroundExecutor(@Background looper: Looper): Executor = ExecutorImpl(looper) + + /** Provide a Background-Thread Executor. */ + @Provides + @SysUISingleton + @Background + fun provideBackgroundDelayableExecutor(@Background looper: Looper): DelayableExecutor = + ExecutorImpl(looper) + + /** Provide a Background-Thread Executor. */ + @Provides + @SysUISingleton + @Background + fun provideBackgroundRepeatableExecutor( + @Background exec: DelayableExecutor + ): RepeatableExecutor = RepeatableExecutorImpl(exec) + + /** Provide a Main-Thread Executor. */ + @Provides + @SysUISingleton + @Main + fun provideMainRepeatableExecutor(@Main exec: DelayableExecutor): RepeatableExecutor = + RepeatableExecutorImpl(exec) + + /** */ + @Provides + @Main + fun providesMainMessageRouter(@Main executor: DelayableExecutor): MessageRouter = + MessageRouterImpl(executor) + + /** */ + @Provides + @Background + fun providesBackgroundMessageRouter(@Background executor: DelayableExecutor): MessageRouter = + MessageRouterImpl(executor) + + /** */ + @Provides + @SysUISingleton + @Named(Dependency.TIME_TICK_HANDLER_NAME) + fun provideTimeTickHandler(): Handler { + val thread = HandlerThread("TimeTick") + thread.start() + return Handler(thread.getLooper()) + } + + /** */ + @Provides + @SysUISingleton + @NotifInflation + fun provideNotifInflationExecutor(@NotifInflation looper: Looper): Executor = + ExecutorImpl(looper) +} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt index 1ec86a4d1dfc..8c46f9af04e5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt @@ -88,8 +88,8 @@ fun <T> collectFlow( flow: Flow<T>, consumer: Consumer<T>, state: Lifecycle.State = Lifecycle.State.CREATED, -) { - lifecycle.coroutineScope.launch { +): Job { + return lifecycle.coroutineScope.launch { lifecycle.repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } } } } 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/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt index b6246da4a7dd..c6b0dc542087 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt @@ -30,7 +30,6 @@ import com.android.settingslib.volume.data.repository.AudioSharingRepository import com.android.settingslib.volume.domain.interactor.AudioModeInteractor import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.domain.model.AudioOutputDevice -import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject @@ -58,8 +57,7 @@ constructor( private val bluetoothAdapter: BluetoothAdapter?, private val deviceIconInteractor: DeviceIconInteractor, private val mediaOutputInteractor: MediaOutputInteractor, - private val localMediaRepositoryFactory: LocalMediaRepositoryFactory, - private val audioSharingRepository: AudioSharingRepository, + audioSharingRepository: AudioSharingRepository, ) { val currentAudioDevice: Flow<AudioOutputDevice> = 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..79a4ae74b217 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,25 +18,28 @@ 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.qualifiers.Application +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.util.LocalMediaManagerFactory import javax.inject.Inject import kotlinx.coroutines.CoroutineScope interface LocalMediaRepositoryFactory { - fun create(packageName: String?): LocalMediaRepository + fun create(packageName: String?, coroutineScope: CoroutineScope): LocalMediaRepository } +@SysUISingleton class LocalMediaRepositoryFactoryImpl @Inject constructor( private val eventsReceiver: AudioManagerEventsReceiver, private val localMediaManagerFactory: LocalMediaManagerFactory, - @Application private val coroutineScope: CoroutineScope, ) : LocalMediaRepositoryFactory { - override fun create(packageName: String?): LocalMediaRepository = + override fun create( + packageName: String?, + coroutineScope: CoroutineScope + ): LocalMediaRepository = LocalMediaRepositoryImpl( eventsReceiver, localMediaManagerFactory.create(packageName), diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index 9fbd79accf80..31a89775e916 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -35,6 +35,7 @@ import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.withContext /** Provides observable models about the current media session state. */ @@ -105,12 +107,9 @@ constructor( .filterData() .map { it?.packageName } .distinctUntilChanged() - .map { localMediaRepositoryFactory.create(it) } - .stateIn( - coroutineScope, - SharingStarted.Eagerly, - localMediaRepositoryFactory.create(null) - ) + .transformLatest { + coroutineScope { emit(localMediaRepositoryFactory.create(it, this)) } + } /** Currently connected [MediaDevice]. */ val currentConnectedDevice: Flow<MediaDevice?> = diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index fd01b4864772..850162e65aa6 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -146,14 +146,18 @@ constructor( isEnabled = isEnabled, a11yStep = volumeRange.step, a11yClickDescription = - context.getString( - if (isMuted) { - R.string.volume_panel_hint_unmute - } else { - R.string.volume_panel_hint_mute - }, - label, - ), + if (isAffectedByMute) { + context.getString( + if (isMuted) { + R.string.volume_panel_hint_unmute + } else { + R.string.volume_panel_hint_mute + }, + label, + ) + } else { + null + }, a11yStateDescription = if (volume == volumeRange.first) { context.getString( 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..99f956489bc3 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,31 @@ 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) - } - } + activityStarter.dismissKeyguardThenExecute( + { + volumePanelGlobalStateInteractor.setVisible(true) + false + }, + {}, + 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/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt index bd446b9ed190..1d1329ac550c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile import com.android.systemui.qs.tiles.FontScalingTile +import com.android.systemui.qs.tiles.HearingDevicesTile import com.android.systemui.qs.tiles.OneHandedModeTile import com.android.systemui.qs.tiles.ReduceBrightColorsTile import com.android.systemui.util.mockito.whenever @@ -94,7 +95,7 @@ class AccessibilityQsShortcutsRepositoryImplForDeviceTest : SysuiTestCase() { fun testTileSpecToComponentMappingContent() { val mapping = AccessibilityQsShortcutsRepositoryImpl.TILE_SPEC_TO_COMPONENT_MAPPING - assertThat(mapping.size).isEqualTo(5) + assertThat(mapping.size).isEqualTo(6) assertThat(mapping[ColorCorrectionTile.TILE_SPEC]) .isEqualTo(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME) assertThat(mapping[ColorInversionTile.TILE_SPEC]) @@ -107,6 +108,10 @@ class AccessibilityQsShortcutsRepositoryImplForDeviceTest : SysuiTestCase() { ) assertThat(mapping[FontScalingTile.TILE_SPEC]) .isEqualTo(AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME) + assertThat(mapping[HearingDevicesTile.TILE_SPEC]) + .isEqualTo( + AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index 8895a5e1a97c..0db0de2bcd7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility.hearingaid; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesDialogDelegate.LIVE_CAPTION_INTENT; import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK; import static com.google.common.truth.Truth.assertThat; @@ -24,16 +25,24 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Handler; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import android.widget.LinearLayout; import androidx.test.filters.SmallTest; @@ -44,6 +53,7 @@ import com.android.settingslib.bluetooth.HapClientProfile; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.bluetooth.qsdialog.DeviceItem; @@ -54,6 +64,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.phone.SystemUIDialogManager; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -75,6 +86,10 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { public MockitoRule mockito = MockitoJUnit.rule(); private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"; + private static final String TEST_PKG = "pkg"; + private static final String TEST_CLS = "cls"; + private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PKG, TEST_CLS); + private static final String TEST_LABEL = "label"; @Mock private SystemUIDialog.Factory mSystemUIDialogFactory; @@ -104,6 +119,12 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { private CachedBluetoothDevice mCachedDevice; @Mock private DeviceItem mHearingDeviceItem; + @Mock + private PackageManager mPackageManager; + @Mock + private ActivityInfo mActivityInfo; + @Mock + private Drawable mDrawable; private SystemUIDialog mDialog; private HearingDevicesDialogDelegate mDialogDelegate; private TestableLooper mTestableLooper; @@ -122,6 +143,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS); when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice); + mContext.setMockPackageManager(mPackageManager); setUpPairNewDeviceDialog(); @@ -170,6 +192,45 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { verify(mCachedDevice).disconnect(); } + @Test + @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS) + public void showDialog_hasLiveCaption_noRelatedToolsInConfig_showOneRelatedTool() { + when(mPackageManager.queryIntentActivities( + eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn( + List.of(new ResolveInfo())); + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{}); + + setUpPairNewDeviceDialog(); + mDialog.show(); + + LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); + assertThat(relatedToolsView.getChildCount()).isEqualTo(1); + } + + @Test + @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS) + public void showDialog_hasLiveCaption_oneRelatedToolInConfig_showTwoRelatedTools() + throws PackageManager.NameNotFoundException { + when(mPackageManager.queryIntentActivities( + eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn( + List.of(new ResolveInfo())); + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsHearingDevicesRelatedToolName, + new String[]{TEST_PKG + "/" + TEST_CLS}); + when(mPackageManager.getActivityInfo(eq(TEST_COMPONENT), anyInt())).thenReturn( + mActivityInfo); + when(mActivityInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL); + when(mActivityInfo.loadIcon(mPackageManager)).thenReturn(mDrawable); + when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT); + + setUpPairNewDeviceDialog(); + mDialog.show(); + + LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); + assertThat(relatedToolsView.getChildCount()).isEqualTo(2); + } + private void setUpPairNewDeviceDialog() { mDialogDelegate = new HearingDevicesDialogDelegate( mContext, @@ -219,4 +280,18 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { private View getPairNewDeviceButton(SystemUIDialog dialog) { return dialog.requireViewById(R.id.pair_new_device_button); } + + private View getRelatedToolsView(SystemUIDialog dialog) { + return dialog.requireViewById(R.id.related_tools_container); + } + + @After + public void reset() { + if (mDialogDelegate != null) { + mDialogDelegate = null; + } + if (mDialog != null) { + mDialog.dismiss(); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java new file mode 100644 index 000000000000..717292378913 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java @@ -0,0 +1,134 @@ +/* + * 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.accessibility.hearingaid; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import static java.util.Collections.emptyList; + +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +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.util.List; + +/** + * Tests for {@link HearingDevicesToolItemParser}. + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class HearingDevicesToolItemParserTest extends SysuiTestCase { + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private PackageManager mPackageManager; + @Mock + private ActivityInfo mActivityInfo; + @Mock + private Drawable mDrawable; + private static final String TEST_PKG = "pkg"; + private static final String TEST_CLS = "cls"; + private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PKG, TEST_CLS); + private static final String TEST_NO_EXIST_PKG = "NoPkg"; + private static final String TEST_NO_EXIST_CLS = "NoCls"; + private static final ComponentName TEST_NO_EXIST_COMPONENT = new ComponentName( + TEST_NO_EXIST_PKG, TEST_NO_EXIST_CLS); + + private static final String TEST_LABEL = "label"; + + @Before + public void setUp() throws PackageManager.NameNotFoundException { + mContext.setMockPackageManager(mPackageManager); + + when(mPackageManager.getActivityInfo(eq(TEST_COMPONENT), anyInt())).thenReturn( + mActivityInfo); + when(mPackageManager.getActivityInfo(eq(TEST_NO_EXIST_COMPONENT), anyInt())).thenThrow( + new PackageManager.NameNotFoundException()); + when(mActivityInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL); + when(mActivityInfo.loadIcon(mPackageManager)).thenReturn(mDrawable); + when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT); + } + + @Test + public void parseStringArray_noString_emptyResult() { + assertThat(HearingDevicesToolItemParser.parseStringArray(mContext, new String[]{}, + new String[]{})).isEqualTo(emptyList()); + } + + @Test + public void parseStringArray_oneToolName_oneExpectedToolItem() { + String[] toolName = new String[]{TEST_PKG + "/" + TEST_CLS}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + toolName, new String[]{}); + + assertThat(toolItemList.size()).isEqualTo(1); + assertThat(toolItemList.get(0).getToolName()).isEqualTo(TEST_LABEL); + assertThat(toolItemList.get(0).getToolIntent().getComponent()).isEqualTo(TEST_COMPONENT); + } + + @Test + public void parseStringArray_fourToolName_maxThreeToolItem() { + String componentNameString = TEST_PKG + "/" + TEST_CLS; + String[] fourToolName = + new String[]{componentNameString, componentNameString, componentNameString, + componentNameString}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + fourToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(HearingDevicesToolItemParser.MAX_NUM); + } + + @Test + public void parseStringArray_oneWrongFormatToolName_noToolItem() { + String[] wrongFormatToolName = new String[]{TEST_PKG}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + wrongFormatToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(0); + } + + @Test + public void parseStringArray_oneNotExistToolName_noToolItem() { + String[] notExistToolName = new String[]{TEST_NO_EXIST_PKG + "/" + TEST_NO_EXIST_CLS}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + notExistToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(0); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java index d01d57e18223..358e8cbd4a3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java @@ -22,6 +22,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -45,6 +47,7 @@ import android.view.WindowMetrics; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import androidx.test.filters.SmallTest; @@ -67,6 +70,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -119,6 +123,8 @@ public class TouchMonitorTest extends SysuiTestCase { private final KosmosJavaAdapter mKosmos; + private ArrayList<LifecycleObserver> mLifecycleObservers = new ArrayList<>(); + Environment(Set<TouchHandler> handlers, KosmosJavaAdapter kosmos) { mLifecycleOwner = new SimpleLifecycleOwner(); @@ -147,8 +153,14 @@ public class TouchMonitorTest extends SysuiTestCase { mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor, mLifecycleRegistry, mInputFactory, mDisplayHelper, mKosmos.getConfigurationInteractor(), handlers, mIWindowManager, 0); + clearInvocations(mLifecycleRegistry); mMonitor.init(); + ArgumentCaptor<LifecycleObserver> observerCaptor = + ArgumentCaptor.forClass(LifecycleObserver.class); + verify(mLifecycleRegistry, atLeast(1)).addObserver(observerCaptor.capture()); + mLifecycleObservers.addAll(observerCaptor.getAllValues()); + updateLifecycle(Lifecycle.State.RESUMED); // Capture creation request. @@ -187,6 +199,16 @@ public class TouchMonitorTest extends SysuiTestCase { verify(mInputSession).dispose(); Mockito.clearInvocations(mInputSession); } + + void destroyMonitor() { + mMonitor.destroy(); + } + + void verifyLifecycleObserversUnregistered() { + for (LifecycleObserver observer : mLifecycleObservers) { + verify(mLifecycleRegistry).removeObserver(observer); + } + } } @Test @@ -642,6 +664,16 @@ public class TouchMonitorTest extends SysuiTestCase { verify(callback).onRemoved(); } + @Test + public void testDestroy_cleansUpLifecycleObserver() { + final TouchHandler touchHandler = createTouchHandler(); + + final Environment environment = new Environment(Stream.of(touchHandler) + .collect(Collectors.toCollection(HashSet::new)), mKosmos); + environment.destroyMonitor(); + environment.verifyLifecycleObserversUnregistered(); + } + private GestureDetector.OnGestureListener registerGestureListener(TouchHandler handler) { final GestureDetector.OnGestureListener gestureListener = Mockito.mock( GestureDetector.OnGestureListener.class); 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/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index e81369d9631c..9a99ed7bb512 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.biometrics import android.app.ActivityTaskManager import android.app.admin.DevicePolicyManager import android.content.pm.PackageManager +import android.content.res.Configuration import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricManager @@ -127,6 +128,12 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var packageManager: PackageManager @Mock private lateinit var activityTaskManager: ActivityTaskManager + private lateinit var displayRepository: FakeDisplayRepository + private lateinit var displayStateInteractor: DisplayStateInteractor + private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor + private lateinit var biometricStatusInteractor: BiometricStatusInteractor + private lateinit var iconProvider: IconProvider + private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val biometricPromptRepository = FakePromptRepository() @@ -142,17 +149,12 @@ open class AuthContainerViewTest : SysuiTestCase() { private val promptSelectorInteractor by lazy { PromptSelectorInteractorImpl( fingerprintRepository, + displayStateInteractor, biometricPromptRepository, lockPatternUtils, ) } - private lateinit var displayRepository: FakeDisplayRepository - private lateinit var displayStateInteractor: DisplayStateInteractor - private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor - private lateinit var biometricStatusInteractor: BiometricStatusInteractor - private lateinit var iconProvider: IconProvider - private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) @@ -392,6 +394,33 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test + fun testAnimateToCredentialUI_rotateCredentialUI() { + val container = initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or + BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) + container.animateToCredentialUI(false) + waitForIdleSync() + + assertThat(container.hasCredentialView()).isTrue() + assertThat(container.hasBiometricPrompt()).isFalse() + + // Check credential view persists after new attachment + container.onAttachedToWindow() + + assertThat(container.hasCredentialView()).isTrue() + assertThat(container.hasBiometricPrompt()).isFalse() + + val configuration = Configuration(context.resources.configuration) + configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + container.dispatchConfigurationChanged(configuration) + waitForIdleSync() + + assertThat(container.hasCredentialView()).isTrue() + assertThat(container.hasBiometricPrompt()).isFalse() + } + + @Test fun testShowBiometricUI() { mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) val container = initializeFingerprintContainer() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index 3102a84b852a..6e78e334891b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -25,13 +25,16 @@ import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.faceSensorPropertiesInternal import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal import com.android.systemui.biometrics.shared.model.BiometricModalities +import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever @@ -75,12 +78,30 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { private val promptRepository = FakePromptRepository() private val fakeExecutor = FakeExecutor(FakeSystemClock()) + private lateinit var displayStateRepository: FakeDisplayStateRepository + private lateinit var displayRepository: FakeDisplayRepository + private lateinit var displayStateInteractor: DisplayStateInteractor private lateinit var interactor: PromptSelectorInteractor @Before fun setup() { + displayStateRepository = FakeDisplayStateRepository() + displayRepository = FakeDisplayRepository() + displayStateInteractor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + displayStateRepository, + displayRepository, + ) interactor = - PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) + PromptSelectorInteractorImpl( + fingerprintRepository, + displayStateInteractor, + promptRepository, + lockPatternUtils + ) } private fun basicPromptInfo() = @@ -155,7 +176,8 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { modalities, CHALLENGE, OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ + onSwitchToCredential = false, + isLandscape = false, ) assertThat(currentPrompt).isNotNull() @@ -200,22 +222,49 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { fun promptKind_isBiometric_whenBiometricAllowed() = testScope.runTest { setUserCredentialType(isPassword = true) - val info = basicPromptInfo() val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ - ) + setPrompt() + + assertThat(promptKind?.isOnePanePortraitBiometric()).isTrue() + + interactor.resetPrompt(REQUEST_ID) + verifyUnset() + } + + @Test + fun promptKind_isBiometricTwoPane_whenBiometricAllowed_landscape() = + testScope.runTest { + setUserCredentialType(isPassword = true) + displayStateRepository.setIsLargeScreen(false) + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + setPrompt() + + assertThat(promptKind?.isTwoPaneLandscapeBiometric()).isTrue() + + interactor.resetPrompt(REQUEST_ID) + verifyUnset() + } + + @Test + fun promptKind_isBiometricOnePane_whenBiometricAllowed_largeScreenLandscape() = + testScope.runTest { + setUserCredentialType(isPassword = true) + displayStateRepository.setIsLargeScreen(true) + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) - assertThat(promptKind?.isBiometric()).isTrue() + setPrompt() + + assertThat(promptKind?.isOnePaneLargeScreenLandscapeBiometric()).isTrue() interactor.resetPrompt(REQUEST_ID) verifyUnset() @@ -225,20 +274,11 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { fun promptKind_isCredential_onSwitchToCredential() = testScope.runTest { setUserCredentialType(isPassword = true) - val info = basicPromptInfo() val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - true /*onSwitchToCredential*/ - ) + setPrompt(onSwitchToCredential = true) assertThat(promptKind).isEqualTo(PromptKind.Password) @@ -259,15 +299,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ - ) + setPrompt(info) assertThat(promptKind).isEqualTo(PromptKind.Password) @@ -292,15 +324,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ - ) + setPrompt(info) assertThat(promptKind).isEqualTo(PromptKind.Password) @@ -312,6 +336,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { fun promptKind_isBiometric_whenBiometricIsNotAllowed_withVerticalList() = testScope.runTest { setUserCredentialType(isPassword = true) + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) val info = basicPromptInfo().apply { isDeviceCredentialAllowed = true @@ -322,22 +347,32 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ - ) + setPrompt(info) - assertThat(promptKind?.isBiometric()).isTrue() + assertThat(promptKind?.isOnePaneNoSensorLandscapeBiometric()).isTrue() interactor.resetPrompt(REQUEST_ID) verifyUnset() } + private fun setPrompt( + info: PromptInfo = basicPromptInfo(), + onSwitchToCredential: Boolean = false + ) { + interactor.setPrompt( + info, + USER_ID, + REQUEST_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + onSwitchToCredential = onSwitchToCredential, + isLandscape = + displayStateRepository.currentRotation.value == DisplayRotation.ROTATION_90 || + displayStateRepository.currentRotation.value == DisplayRotation.ROTATION_270, + ) + } + private fun TestScope.useCredentialAndReset(kind: PromptKind) { setUserCredentialType( isPin = kind == PromptKind.Pin, @@ -366,7 +401,8 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { BiometricModalities(), CHALLENGE, OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ + onSwitchToCredential = false, + isLandscape = false, ) // not using biometrics, should be null with no fallback option diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index c177511a2df4..1167fce7524b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -1407,7 +1407,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa runningTaskInfo.topActivity = topActivity whenever(activityTaskManager.getTasks(1)).thenReturn(listOf(runningTaskInfo)) selector = - PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) + PromptSelectorInteractorImpl( + fingerprintRepository, + displayStateInteractor, + promptRepository, + lockPatternUtils + ) selector.resetPrompt(REQUEST_ID) viewModel = @@ -1643,7 +1648,8 @@ private fun PromptSelectorInteractor.initializePrompt( BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), CHALLENGE, packageName, - false /*onUseDeviceCredential*/ + onSwitchToCredential = false, + isLandscape = false, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java index 07c980bb6656..18bd960b30a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java @@ -132,7 +132,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -145,7 +145,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -158,7 +158,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -171,7 +171,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(true); @@ -184,7 +184,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(true); @@ -197,7 +197,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setServiceAvailable(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 36bfa092c042..90ac05fc1b2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -135,6 +135,7 @@ class CustomizationProviderTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = 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/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt index d3c48483d100..65aa825e487d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt @@ -38,7 +38,7 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags 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.shared.model.CommunalScenes import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -110,7 +110,7 @@ class FromOccludedTransitionInteractorTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() = testScope.runTest { - kosmos.fakeCommunalRepository.setTransitionState( + kosmos.fakeCommunalSceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt index 1839d8d15b0c..14f2d654a031 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt @@ -23,7 +23,7 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.SysuiTestCase 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.shared.model.CommunalScenes import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -170,7 +170,7 @@ class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() { fun testReturnToGlanceableHub_whenBouncerHides_ifIdleOnCommunal() = testScope.runTest { underTest.start() - kosmos.fakeCommunalRepository.setTransitionState( + kosmos.fakeCommunalSceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) bouncerRepository.setPrimaryShow(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt index 0bdf47a51670..f0ad5103e9a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -48,6 +49,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -57,6 +61,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val underTest = kosmos.keyguardBlueprintInteractor + private val keyguardBlueprintRepository = kosmos.keyguardBlueprintRepository private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository } private val configurationRepository by lazy { kosmos.fakeConfigurationRepository } private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository } @@ -103,44 +108,46 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun fingerprintPropertyInitialized_updatesBlueprint() { + @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun testDoesNotApplySplitShadeBlueprint() { testScope.runTest { - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull() - - fingerprintPropertyRepository.supportsUdfps() // initialize properties + val blueprintId by collectLastValue(underTest.blueprintId) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + clockRepository.setCurrentClock(clockController) + configurationRepository.onConfigurationChange() runCurrent() advanceUntilIdle() - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull() + assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.DEFAULT) } } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testDoesNotApplySplitShadeBlueprint() { + @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun fingerprintPropertyInitialized_updatesBlueprint() { testScope.runTest { - val blueprintId by collectLastValue(underTest.blueprintId) - kosmos.shadeRepository.setShadeMode(ShadeMode.Split) - clockRepository.setCurrentClock(clockController) - configurationRepository.onConfigurationChange() + underTest.start() + reset(keyguardBlueprintRepository) + + fingerprintPropertyRepository.supportsUdfps() // initialize properties runCurrent() advanceUntilIdle() - assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.DEFAULT) + verify(keyguardBlueprintRepository, times(2)).refreshBlueprint(any()) } } @Test fun testRefreshFromConfigChange() { testScope.runTest { - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull() + underTest.start() + reset(keyguardBlueprintRepository) configurationRepository.onConfigurationChange() runCurrent() advanceUntilIdle() - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull() + verify(keyguardBlueprintRepository, times(2)).refreshBlueprint(any()) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 35659c12fad5..14d954873f0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -281,6 +281,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index ef3183a8891f..ced3526f40be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -281,6 +281,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = 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/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 616aac7ce460..344e0fc2bc6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.mockito.whenever import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) +@ExperimentalCoroutinesApi @SmallTest class DefaultKeyguardBlueprintTest : SysuiTestCase() { private lateinit var underTest: DefaultKeyguardBlueprint @@ -112,17 +114,66 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Test fun replaceViews_withPrevBlueprint() { val prevBlueprint = mock(KeyguardBlueprint::class.java) - val someSection = mock(KeyguardSection::class.java) - whenever(prevBlueprint.sections) - .thenReturn(underTest.sections.minus(mDefaultDeviceEntrySection).plus(someSection)) + val removedSection = mock(KeyguardSection::class.java) + val addedSection = mDefaultDeviceEntrySection + val rebuildSection = clockSection + val prevSections = underTest.sections.minus(addedSection).plus(removedSection) + val unchangedSections = underTest.sections.subtract(listOf(addedSection, rebuildSection)) + whenever(prevBlueprint.sections).thenReturn(prevSections) + val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(constraintLayout, prevBlueprint) - underTest.sections.minus(mDefaultDeviceEntrySection).forEach { - verify(it, never())?.addViews(constraintLayout) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) + } + + verify(addedSection).addViews(constraintLayout) + verify(removedSection).removeViews(constraintLayout) + } + + @Test + fun replaceViews_withPrevBlueprint_withRebuildTargets() { + val prevBlueprint = mock(KeyguardBlueprint::class.java) + val removedSection = mock(KeyguardSection::class.java) + val addedSection = mDefaultDeviceEntrySection + val rebuildSection = clockSection + val prevSections = underTest.sections.minus(addedSection).plus(removedSection) + val unchangedSections = underTest.sections.subtract(listOf(addedSection, rebuildSection)) + whenever(prevBlueprint.sections).thenReturn(prevSections) + + val constraintLayout = ConstraintLayout(context, null) + underTest.replaceViews(constraintLayout, prevBlueprint, listOf(rebuildSection)) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) + } + + verify(addedSection).addViews(constraintLayout) + verify(rebuildSection).addViews(constraintLayout) + verify(rebuildSection).removeViews(constraintLayout) + verify(removedSection).removeViews(constraintLayout) + } + + @Test + fun rebuildViews() { + val rebuildSections = listOf(mDefaultDeviceEntrySection, clockSection) + val unchangedSections = underTest.sections.subtract(rebuildSections) + + val constraintLayout = ConstraintLayout(context, null) + underTest.rebuildViews(constraintLayout, rebuildSections) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) } - verify(mDefaultDeviceEntrySection).addViews(constraintLayout) - verify(someSection).removeViews(constraintLayout) + rebuildSections.forEach { + verify(it).addViews(constraintLayout) + verify(it).removeViews(constraintLayout) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt index a18b0330d48f..ec2a1d305ab3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt @@ -17,9 +17,11 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.os.fakeExecutorHandler import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor +import com.android.systemui.testKosmos import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -33,12 +35,16 @@ import org.mockito.MockitoAnnotations class KeyguardBlueprintViewModelTest : SysuiTestCase() { @Mock private lateinit var keyguardBlueprintInteractor: KeyguardBlueprintInteractor private lateinit var undertest: KeyguardBlueprintViewModel + private val kosmos = testKosmos() @Before fun setup() { MockitoAnnotations.initMocks(this) undertest = - KeyguardBlueprintViewModel(keyguardBlueprintInteractor = keyguardBlueprintInteractor) + KeyguardBlueprintViewModel( + handler = kosmos.fakeExecutorHandler, + keyguardBlueprintInteractor = keyguardBlueprintInteractor, + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 16421a0f83b4..bdc5fc34158f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -178,6 +178,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 833822076620..c1e3e84f2bf4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -49,6 +49,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter @@ -221,6 +222,7 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = @@ -288,6 +290,7 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { underTest = KeyguardQuickAffordancesCombinedViewModel( + applicationScope = kosmos.applicationCoroutineScope, quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt index 3bf4173cd7c7..3906c40593f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt @@ -191,7 +191,7 @@ class MediaDataProcessorTest : SysuiTestCase() { @Before fun setup() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) staticMockSession = ExtendedMockito.mockitoSession() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt index d2701dd0d3a3..16d8819f13fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt @@ -29,6 +29,7 @@ import android.media.session.MediaController.PlaybackInfo import android.media.session.MediaSession import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -40,6 +41,7 @@ import com.android.settingslib.flags.Flags import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice +import com.android.settingslib.media.flags.Flags.FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.shared.model.MediaData @@ -101,7 +103,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var listener: MediaDeviceManager.Listener @Mock private lateinit var device: MediaDevice @Mock private lateinit var icon: Drawable - @Mock private lateinit var route: RoutingSessionInfo + @Mock private lateinit var routingSession: RoutingSessionInfo @Mock private lateinit var selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @@ -141,7 +143,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm) whenever(muteAwaitFactory.create(lmm)).thenReturn(muteAwaitManager) whenever(lmm.getCurrentConnectedDevice()).thenReturn(device) - whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route) + whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(routingSession) + + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) + whenever(controller.playbackInfo).thenReturn(playbackInfo) // Create a media sesssion and notification for testing. session = MediaSession(context, SESSION_KEY) @@ -235,6 +240,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { reset(listener) // WHEN media data is loaded with a different token // AND that token results in a null route + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -394,9 +400,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceNameFromMR2RouteInfo() { + fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName() { // GIVEN that MR2Manager returns a valid routing session - whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -408,8 +415,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfo() { + fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN that MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) @@ -422,13 +430,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceChanged() { + fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN a notif is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) // AND MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN the selected device changes state val deviceCallback = captureCallback() @@ -442,13 +451,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceListUpdate() { + fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN a notif is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) // GIVEN that MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN the selected device changes state val deviceCallback = captureCallback() @@ -461,15 +471,17 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.name).isNull() } + // With the flag enabled, MediaDeviceManager no longer gathers device name information directly. + @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) @Test fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { // When the routing session name is null, and is a system session for a PhoneMediaDevice val phoneDevice = mock(PhoneMediaDevice::class.java) whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) - whenever(route.isSystemSession).thenReturn(true) + whenever(routingSession.isSystemSession).thenReturn(true) - whenever(route.name).thenReturn(null) + whenever(routingSession.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) @@ -483,13 +495,15 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context)) } + // With the flag enabled, MediaDeviceManager no longer gathers device name information directly. + @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) @Test fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { // When the routing session does not have a name, and is a system session - whenever(route.name).thenReturn(null) + whenever(routingSession.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) - whenever(route.isSystemSession).thenReturn(true) + whenever(routingSession.isSystemSession).thenReturn(true) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -503,8 +517,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name - whenever(route.name).thenReturn(null) - whenever(route.isSystemSession).thenReturn(false) + whenever(routingSession.name).thenReturn(null) + whenever(routingSession.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -534,7 +548,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun audioInfoVolumeControlIdChanged() { + fun onAudioInfoChanged_withRemotePlaybackInfo_queriesRoutingSession() { whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) whenever(playbackInfo.getVolumeControlId()).thenReturn(null) whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo) @@ -545,10 +559,11 @@ public class MediaDeviceManagerTest : SysuiTestCase() { reset(mr2) // WHEN onAudioInfoChanged fires with a volume control id change whenever(playbackInfo.getVolumeControlId()).thenReturn("placeholder id") + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java) verify(controller).registerCallback(captor.capture()) captor.value.onAudioInfoChanged(playbackInfo) - // THEN the route is checked + // THEN the routing session is checked verify(mr2).getRoutingSessionForMediaController(eq(controller)) } @@ -654,7 +669,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun testRemotePlaybackDeviceOverride() { - whenever(route.name).thenReturn(DEVICE_NAME) + whenever(routingSession.name).thenReturn(DEVICE_NAME) val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null, showBroadcastButton = false) val mediaDataWithDevice = mediaData.copy(device = deviceData) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt index 5f7c3869fee7..20fb7014b146 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt @@ -29,7 +29,9 @@ import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.domain.pipeline.MediaDataManager @@ -49,7 +51,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.testKosmos import com.android.systemui.util.animation.UniqueObjectHostView -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.FakeSettings @@ -75,6 +76,8 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -112,12 +115,14 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private val testScope = kosmos.testScope private lateinit var mediaHierarchyManager: MediaHierarchyManager private lateinit var isQsBypassingShade: MutableStateFlow<Boolean> + private lateinit var shadeExpansion: MutableStateFlow<Float> private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() private val settings = FakeSettings() private lateinit var testableLooper: TestableLooper private lateinit var fakeHandler: FakeHandler private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository @Before fun setup() { @@ -129,7 +134,9 @@ class MediaHierarchyManagerTest : SysuiTestCase() { fakeHandler = FakeHandler(testableLooper.looper) whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) isQsBypassingShade = MutableStateFlow(false) + shadeExpansion = MutableStateFlow(0f) whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade) + whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion) whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false) mediaHierarchyManager = MediaHierarchyManager( @@ -141,6 +148,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { mediaDataManager, keyguardViewController, dreamOverlayStateController, + kosmos.keyguardInteractor, kosmos.communalTransitionViewModel, configurationController, wakefulnessLifecycle, @@ -191,7 +199,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -204,7 +212,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController, times(0)) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -218,7 +226,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -231,7 +239,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController, times(0)) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -245,7 +253,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -255,7 +263,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -269,7 +277,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -281,7 +289,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -297,7 +305,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QQS), - any(MediaHostState::class.java), + any<MediaHostState>(), eq(false), anyLong(), anyLong() @@ -309,7 +317,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_LOCKSCREEN), - any(MediaHostState::class.java), + any<MediaHostState>(), eq(false), anyLong(), anyLong() @@ -482,6 +490,26 @@ class MediaHierarchyManagerTest : SysuiTestCase() { } @Test + fun isCurrentlyInGuidedTransformation_desiredLocationIsHub_returnsFalse() = + testScope.runTest { + goToLockscreen() + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + testScope = testScope, + ) + mediaHierarchyManager.qsExpansion = 0f + mediaHierarchyManager.setTransitionToFullShadeAmount(123f) + + whenever(lockHost.visible).thenReturn(true) + whenever(qsHost.visible).thenReturn(true) + whenever(qqsHost.visible).thenReturn(true) + whenever(hubModeHost.visible).thenReturn(true) + + assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() + } + + @Test fun testDream() { goToDream() setMediaDreamComplicationEnabled(true) @@ -499,7 +527,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QQS), - any(MediaHostState::class.java), + any<MediaHostState>(), eq(false), anyLong(), anyLong() @@ -532,7 +560,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QQS), - any(MediaHostState::class.java), + any<MediaHostState>(), eq(false), anyLong(), anyLong() @@ -590,7 +618,50 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QS), - any(MediaHostState::class.java), + any<MediaHostState>(), + eq(false), + anyLong(), + anyLong() + ) + } + + @Test + fun testCommunalLocation_whenDreamingAndShadeExpanding() = + testScope.runTest { + keyguardRepository.setDreaming(true) + runCurrent() + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + testScope = testScope, + ) + // Mock the behavior for dreaming that pulling down shade will immediately set QS as + // expanded + expandQS() + // Starts opening the shade + shadeExpansion.value = 0.1f + runCurrent() + + // UMO shows on hub + verify(mediaCarouselController) + .onDesiredLocationChanged( + eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), + anyOrNull(), + eq(false), + anyLong(), + anyLong() + ) + clearInvocations(mediaCarouselController) + + // The shade is opened enough to make QS elements visible + shadeExpansion.value = 0.5f + runCurrent() + + // UMO shows on QS + verify(mediaCarouselController) + .onDesiredLocationChanged( + eq(MediaHierarchyManager.LOCATION_QS), + any<MediaHostState>(), eq(false), anyLong(), anyLong() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt index e5d3082bb245..80ebe56453ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt @@ -376,7 +376,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) getEnabledChangeListener().onEnabledChanged(enabled = true) @@ -388,7 +388,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun attachPlayer_seekBarEnabled_seekBarVisible() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) getEnabledChangeListener().onEnabledChanged(enabled = true) @@ -399,7 +399,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) getEnabledChangeListener().onEnabledChanged(enabled = true) @@ -415,7 +415,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun attachPlayer_notScrubbing_scrubbingViewsGone() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) mediaViewController.canShowScrubbingTime = true @@ -435,7 +435,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) mediaViewController.canShowScrubbingTime = false @@ -454,7 +454,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(true) @@ -476,7 +476,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(false) @@ -498,7 +498,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(true) @@ -524,7 +524,7 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt index 6043ede66b31..e4877808f133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.mediaprojection.taskswitcher.data.repository +package com.android.systemui.mediaprojection.data.repository import android.os.Binder import android.testing.AndroidTestingRunner @@ -23,12 +23,11 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken -import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager -import com.android.systemui.mediaprojection.taskswitcher.mediaProjectionManagerRepository import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -45,7 +44,7 @@ class MediaProjectionManagerRepositoryTest : SysuiTestCase() { private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager - private val repo = kosmos.mediaProjectionManagerRepository + private val repo = kosmos.realMediaProjectionRepository @Test fun switchProjectedTask_stateIsUpdatedWithNewTask() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt index 9382c5882b25..ad18099fefb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt @@ -40,6 +40,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify @RunWith(AndroidTestingRunner::class) @@ -66,6 +67,10 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { fakeBroadcastDispatcher, ) coordinator.start() + // When the coordinator starts up, the view model will immediately emit a NotShowing event + // and hide the notification. That's fine, but we should reset the notification manager so + // that the initial emission isn't part of the tests. + reset(notificationManager) } @Test @@ -82,8 +87,13 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { @Test fun hideNotification() { testScope.runTest { + // First, show a notification + switchTask() + + // WHEN the projection is stopped fakeMediaProjectionManager.dispatchOnStop() + // THEN the notification is hidden verify(notificationManager).cancel(any(), any()) } } @@ -91,14 +101,16 @@ class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() { @Test fun notificationIdIsConsistent() { testScope.runTest { - fakeMediaProjectionManager.dispatchOnStop() - val idCancel = argumentCaptor<Int>() - verify(notificationManager).cancel(any(), idCancel.capture()) - + // First, show a notification switchTask() val idNotify = argumentCaptor<Int>() verify(notificationManager).notify(any(), idNotify.capture(), any()) + // Then, hide the notification + fakeMediaProjectionManager.dispatchOnStop() + val idCancel = argumentCaptor<Int>() + verify(notificationManager).cancel(any(), idCancel.capture()) + assertEquals(idCancel.value, idNotify.value) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt index f1c97dd45f09..23cf7fb9c73d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt @@ -55,6 +55,7 @@ class BackPanelControllerTest : SysuiTestCase() { companion object { private const val START_X: Float = 0f } + private val kosmos = testKosmos() private lateinit var mBackPanelController: BackPanelController private lateinit var systemClock: FakeSystemClock diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java index 6956beab418e..09d6a1a18ccd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java @@ -39,7 +39,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; -import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; @@ -51,6 +50,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.compose.ui.platform.ComposeView; import androidx.lifecycle.Lifecycle; import androidx.test.filters.SmallTest; @@ -58,12 +58,10 @@ import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.EnableSceneContainer; -import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSComponent; import com.android.systemui.qs.external.TileServiceRequestController; -import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; @@ -112,9 +110,7 @@ public class QSImplTest extends SysuiTestCase { @Mock private QSSquishinessController mSquishinessController; @Mock private FooterActionsViewModel mFooterActionsViewModel; @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory; - @Mock private FooterActionsViewBinder mFooterActionsViewBinder; @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - @Mock private FeatureFlagsClassic mFeatureFlags; private ViewGroup mQsView; private final CommandQueue mCommandQueue = @@ -259,6 +255,39 @@ public class QSImplTest extends SysuiTestCase { } @Test + public void setQsExpansion_whenShouldUpdateSquishinessTrue_setsSquishinessBasedOnFraction() { + enableSplitShade(); + when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); + float expansion = 0.456f; + float panelExpansionFraction = 0.678f; + float proposedTranslation = 567f; + float squishinessFraction = 0.789f; + + mUnderTest.setShouldUpdateSquishinessOnMedia(true); + mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, + squishinessFraction); + + verify(mQSMediaHost).setSquishFraction(squishinessFraction); + } + + @Test + public void setQsExpansion_whenOnKeyguardAndShouldUpdateSquishinessFalse_setsSquishiness() { + // Random test values without any meaning. They just have to be different from each other. + float expansion = 0.123f; + float panelExpansionFraction = 0.321f; + float proposedTranslation = 456f; + float squishinessFraction = 0.567f; + + enableSplitShade(); + setStatusBarCurrentAndUpcomingState(KEYGUARD); + mUnderTest.setShouldUpdateSquishinessOnMedia(false); + mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, + squishinessFraction); + + verify(mQSMediaHost).setSquishFraction(1.0f); + } + + @Test public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() { // Random test values without any meaning. They just have to be different from each other. float expansion = 0.123f; @@ -496,18 +525,13 @@ public class QSImplTest extends SysuiTestCase { @Test @EnableSceneContainer public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() { - clearInvocations( - mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory); + clearInvocations(mFooterActionsViewModel, mFooterActionsViewModelFactory); QSImpl other = instantiate(); other.onComponentCreated(mQsComponent, null); assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull(); - verifyZeroInteractions( - mFooterActionsViewModel, - mFooterActionsViewBinder, - mFooterActionsViewModelFactory - ); + verifyZeroInteractions(mFooterActionsViewModel, mFooterActionsViewModelFactory); } @Test @@ -553,9 +577,7 @@ public class QSImplTest extends SysuiTestCase { mock(QSLogger.class), mock(FooterActionsController.class), mFooterActionsViewModelFactory, - mFooterActionsViewBinder, - mLargeScreenShadeInterpolator, - mFeatureFlags + mLargeScreenShadeInterpolator ); } @@ -589,41 +611,20 @@ public class QSImplTest extends SysuiTestCase { customizer.setId(android.R.id.edit); mQsView.addView(customizer); - View footerActionsView = new FooterActionsViewBinder().create(mContext); + ComposeView footerActionsView = new ComposeView(mContext); footerActionsView.setId(R.id.qs_footer_actions); mQsView.addView(footerActionsView); } private void setUpInflater() { - LayoutInflater realInflater = LayoutInflater.from(mContext); - when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater); when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean())) - .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0), - (ViewGroup) invocation.getArgument(1), - (boolean) invocation.getArgument(2))); + .thenAnswer((invocation) -> mQsView); when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class))) - .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0), - (ViewGroup) invocation.getArgument(1))); + .thenAnswer((invocation) -> mQsView); mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater); } - private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root) { - return inflate(realInflater, layoutRes, root, root != null); - } - - private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root, - boolean attachToRoot) { - if (layoutRes == R.layout.footer_actions - || layoutRes == R.layout.footer_actions_text_button - || layoutRes == R.layout.footer_actions_number_button - || layoutRes == R.layout.footer_actions_icon_button) { - return realInflater.inflate(layoutRes, root, attachToRoot); - } - - return mQsView; - } - private void setupQsComponent() { when(mQsComponent.getQSPanelController()).thenReturn(mQSPanelController); when(mQsComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt new file mode 100644 index 000000000000..07ec38e6ae6c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import android.content.res.Configuration +import android.content.res.Resources +import android.testing.TestableLooper.RunWithLooper +import android.view.ViewTreeObserver +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.haptics.qs.QSLongPressEffect +import com.android.systemui.haptics.qs.qsLongPressEffect +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.InstantTaskExecutorRule +import com.android.systemui.media.controls.ui.view.MediaHost +import com.android.systemui.qs.customize.QSCustomizerController +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.statusbar.policy.SplitShadeStateController +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import javax.inject.Provider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +@SmallTest +@RunWithLooper +@OptIn(ExperimentalCoroutinesApi::class) +@EnableSceneContainer +class QSPanelControllerBaseSceneContainerTest : SysuiTestCase() { + + @Rule @JvmField val mInstantTaskExecutor = InstantTaskExecutorRule() + + private val kosmos = testKosmos() + + @Mock private lateinit var qsPanel: QSPanel + @Mock private lateinit var qsHost: QSHost + @Mock private lateinit var qsCustomizerController: QSCustomizerController + @Mock private lateinit var metricsLogger: MetricsLogger + private val uiEventLogger = UiEventLoggerFake() + @Mock private lateinit var qsLogger: QSLogger + private val dumpManager = DumpManager() + @Mock private lateinit var tileLayout: PagedTileLayout + @Mock private lateinit var resources: Resources + private val configuration = Configuration() + @Mock private lateinit var viewTreeObserver: ViewTreeObserver + @Mock private lateinit var mediaHost: MediaHost + + private var isSplitShade = false + private val splitShadeStateController = + object : SplitShadeStateController { + override fun shouldUseSplitNotificationShade(resources: Resources): Boolean { + return isSplitShade + } + } + private val longPressEffectProvider: Provider<QSLongPressEffect> = Provider { + kosmos.qsLongPressEffect + } + + private val mediaVisible = MutableStateFlow(false) + + private lateinit var underTest: TestableQSPanelControllerBase + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + allowTestableLooperAsMainThread() + Dispatchers.setMain(kosmos.testDispatcher) + + whenever(qsPanel.isAttachedToWindow).thenReturn(true) + whenever(qsPanel.orCreateTileLayout).thenReturn(tileLayout) + whenever(qsPanel.tileLayout).thenReturn(tileLayout) + whenever(qsPanel.resources).thenReturn(resources) + whenever(qsPanel.viewTreeObserver).thenReturn(viewTreeObserver) + whenever(qsHost.tiles).thenReturn(emptyList()) + whenever(resources.configuration).thenReturn(configuration) + + underTest = createUnderTest() + underTest.init() + } + + @After + fun tearDown() { + disallowTestableLooperAsMainThread() + Dispatchers.resetMain() + } + + @Test + fun configurationChange_onlySplitShadeConfigChanges_horizontalInSceneUpdated() = + with(kosmos) { + testScope.runTest { + clearInvocations(qsPanel) + + mediaVisible.value = true + runCurrent() + isSplitShade = false + configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES + underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration) + + assertThat(underTest.shouldUseHorizontalInScene()).isTrue() + verify(qsPanel).setColumnRowLayout(true) + clearInvocations(qsPanel) + + isSplitShade = true + underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration) + + assertThat(underTest.shouldUseHorizontalInScene()).isFalse() + verify(qsPanel).setColumnRowLayout(false) + } + } + + @Test + fun configurationChange_shouldUseHorizontalInSceneInLongDevices() = + with(kosmos) { + testScope.runTest { + clearInvocations(qsPanel) + + mediaVisible.value = true + runCurrent() + isSplitShade = false + // When device is rotated to landscape and is long + configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES + underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration) + + // Then the layout changes + assertThat(underTest.shouldUseHorizontalInScene()).isTrue() + verify(qsPanel).setColumnRowLayout(true) + clearInvocations(qsPanel) + + // When device changes to not-long + configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_NO + underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration) + + // Then the layout changes back + assertThat(underTest.shouldUseHorizontalInScene()).isFalse() + verify(qsPanel).setColumnRowLayout(false) + } + } + + @Test + fun configurationChange_horizontalInScene_onlyInLandscape() = + with(kosmos) { + testScope.runTest { + clearInvocations(qsPanel) + + mediaVisible.value = true + runCurrent() + isSplitShade = false + + // When device is rotated to landscape and is long + configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES + underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration) + + // Then the layout changes + assertThat(underTest.shouldUseHorizontalInScene()).isTrue() + verify(qsPanel).setColumnRowLayout(true) + clearInvocations(qsPanel) + + // When it is rotated back to portrait + configuration.orientation = Configuration.ORIENTATION_PORTRAIT + underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration) + + // Then the layout changes back + assertThat(underTest.shouldUseHorizontalInScene()).isFalse() + verify(qsPanel).setColumnRowLayout(false) + } + } + + @Test + fun changeMediaVisible_changesHorizontalInScene() = + with(kosmos) { + testScope.runTest { + mediaVisible.value = false + runCurrent() + isSplitShade = false + configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES + underTest.mOnConfigurationChangedListener.onConfigurationChange(configuration) + + assertThat(underTest.shouldUseHorizontalInScene()).isFalse() + clearInvocations(qsPanel) + + mediaVisible.value = true + runCurrent() + + assertThat(underTest.shouldUseHorizontalInScene()).isTrue() + verify(qsPanel).setColumnRowLayout(true) + } + } + + @Test + fun startFromMediaHorizontalLong_shouldUseHorizontal() = + with(kosmos) { + testScope.runTest { + mediaVisible.value = true + runCurrent() + isSplitShade = false + configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + configuration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES + + underTest = createUnderTest() + underTest.init() + runCurrent() + + assertThat(underTest.shouldUseHorizontalInScene()).isTrue() + verify(qsPanel).setColumnRowLayout(true) + } + } + + private fun createUnderTest(): TestableQSPanelControllerBase { + return TestableQSPanelControllerBase( + qsPanel, + qsHost, + qsCustomizerController, + mediaHost, + metricsLogger, + uiEventLogger, + qsLogger, + dumpManager, + splitShadeStateController, + longPressEffectProvider, + mediaVisible, + ) + } + + private class TestableQSPanelControllerBase( + view: QSPanel, + qsHost: QSHost, + qsCustomizerController: QSCustomizerController, + mediaHost: MediaHost, + metricsLogger: MetricsLogger, + uiEventLogger: UiEventLogger, + qsLogger: QSLogger, + dumpManager: DumpManager, + splitShadeStateController: SplitShadeStateController, + longPressEffectProvider: Provider<QSLongPressEffect>, + private val mediaVisibleFlow: StateFlow<Boolean> + ) : + QSPanelControllerBase<QSPanel>( + view, + qsHost, + qsCustomizerController, + /* usingMediaPlayer= */ false, + mediaHost, + metricsLogger, + uiEventLogger, + qsLogger, + dumpManager, + splitShadeStateController, + longPressEffectProvider + ) { + + init { + whenever(view.dumpableTag).thenReturn(hashCode().toString()) + } + override fun getMediaVisibleFlow(): StateFlow<Boolean> { + return mediaVisibleFlow + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index 542bfaaa8484..225adab04ff0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -17,9 +17,13 @@ package com.android.systemui.qs; import static com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS; +import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.asStateFlow; +import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -37,9 +41,10 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; +import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper.RunWithLooper; import android.view.ContextThemeWrapper; +import android.view.ViewTreeObserver; import androidx.test.filters.SmallTest; @@ -49,18 +54,26 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.DisableSceneContainer; import com.android.systemui.haptics.qs.QSLongPressEffect; import com.android.systemui.kosmos.KosmosJavaAdapter; +import com.android.systemui.lifecycle.InstantTaskExecutorRule; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.util.animation.DisappearParameters; +import kotlinx.coroutines.flow.MutableStateFlow; +import kotlinx.coroutines.flow.StateFlow; + +import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -73,11 +86,22 @@ import java.util.List; import javax.inject.Provider; -@RunWith(AndroidTestingRunner.class) +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper @SmallTest public class QSPanelControllerBaseTest extends SysuiTestCase { + @Rule + public final InstantTaskExecutorRule mInstantTaskExecutor = new InstantTaskExecutorRule(); + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return parameterizeSceneContainerFlag(); + } + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); @Mock private QSPanel mQSPanel; @@ -109,10 +133,13 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { Configuration mConfiguration; @Mock Runnable mHorizontalLayoutListener; + @Mock + private ViewTreeObserver mViewTreeObserver; + private TestableLongPressEffectProvider mLongPressEffectProvider = new TestableLongPressEffectProvider(); - private QSPanelControllerBase<QSPanel> mController; + private TestableQSPanelControllerBase mController; /** Implementation needed to ensure we have a reflectively-available class name. */ private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> { @@ -120,15 +147,27 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { QSCustomizerController qsCustomizerController, MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager) { - super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger, + super(view, host, qsCustomizerController, usingMediaPlayer(), + mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, new ResourcesSplitShadeStateController(), mLongPressEffectProvider); } + private MutableStateFlow<Boolean> mMediaVisible = MutableStateFlow(false); + @Override protected QSTileRevealController createTileRevealController() { return mQSTileRevealController; } + + @Override + StateFlow<Boolean> getMediaVisibleFlow() { + return asStateFlow(mMediaVisible); + } + + void setMediaVisible(boolean visible) { + mMediaVisible.tryEmit(visible); + } } private class TestableLongPressEffectProvider implements Provider<QSLongPressEffect> { @@ -142,16 +181,24 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { } } + public QSPanelControllerBaseTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + allowTestableLooperAsMainThread(); + when(mQSPanel.isAttachedToWindow()).thenReturn(true); when(mQSPanel.getDumpableTag()).thenReturn("QSPanel"); when(mQSPanel.openPanelEvent()).thenReturn(QSEvent.QS_PANEL_EXPANDED); when(mQSPanel.closePanelEvent()).thenReturn(QSEvent.QS_PANEL_COLLAPSED); when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout); when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout); + when(mQSPanel.getViewTreeObserver()).thenReturn(mViewTreeObserver); when(mQSTile.getTileSpec()).thenReturn("dnd"); when(mQSHost.getTiles()).thenReturn(Collections.singleton(mQSTile)); when(mQSTileRevealControllerFactory.create(any(), any())) @@ -174,6 +221,11 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { reset(mQSTileRevealController); } + @After + public void tearDown() { + disallowTestableLooperAsMainThread(); + } + @Test public void testSetRevealExpansion_preAttach() { mController.onViewDetached(); @@ -269,6 +321,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { @Test + @DisableSceneContainer public void testShouldUseHorizontalLayout_falseForSplitShade() { mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; mConfiguration.screenLayout = Configuration.SCREENLAYOUT_LONG_YES; @@ -294,6 +347,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testChangeConfiguration_shouldUseHorizontalLayoutInLandscape_true() { when(mMediaHost.getVisible()).thenReturn(true); mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener); @@ -317,6 +371,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void testChangeConfiguration_shouldUseHorizontalLayoutInLongDevices_true() { when(mMediaHost.getVisible()).thenReturn(true); mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener); @@ -353,6 +408,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { } @Test + @DisableSceneContainer public void configurationChange_onlySplitShadeConfigChanges_horizontalLayoutStatusUpdated() { // Preconditions for horizontal layout when(mMediaHost.getVisible()).thenReturn(true); @@ -502,4 +558,20 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { verify(mQSPanel, times(2)).removeTile(any()); verify(mQSPanel, times(2)).addTile(any()); } + + @Test + public void dettach_destroy_attach_tilesAreNotReadded() { + when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile)); + mController.setTiles(); + + mController.onViewDetached(); + mController.destroy(); + mController.onViewAttached(); + + assertThat(mController.mRecords).isEmpty(); + } + + private boolean usingMediaPlayer() { + return !SceneContainerFlag.isEnabled(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index e50320df2740..545d19dd771e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -10,6 +10,7 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.haptics.qs.QSLongPressEffect +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.plugins.FalsingManager @@ -17,6 +18,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.logging.QSLogger import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.settings.brightness.BrightnessController import com.android.systemui.settings.brightness.BrightnessSliderController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager @@ -63,6 +65,9 @@ class QSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var configuration: Configuration @Mock private lateinit var pagedTileLayout: PagedTileLayout @Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect> + @Mock private lateinit var mediaCarouselInteractor: MediaCarouselInteractor + + private val usingMediaPlayer: Boolean by lazy { !SceneContainerFlag.isEnabled } private lateinit var controller: QSPanelController private val testableResources: TestableResources = mContext.orCreateTestableResources @@ -88,7 +93,7 @@ class QSPanelControllerTest : SysuiTestCase() { tunerService, qsHost, qsCustomizerController, - /* usingMediaPlayer= */ true, + /* usingMediaPlayer= */ usingMediaPlayer, mediaHost, qsTileRevealControllerFactory, dumpManager, @@ -101,6 +106,7 @@ class QSPanelControllerTest : SysuiTestCase() { statusBarKeyguardViewManager, ResourcesSplitShadeStateController(), longPressEffectProvider, + mediaCarouselInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index 5c6ed70c85a6..e2a4d6727e80 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -275,6 +275,19 @@ class QSPanelTest : SysuiTestCase() { ViewUtils.detachView(panel) } + @Test + fun setRowColumnLayout() { + qsPanel.setColumnRowLayout(/* withMedia= */ false) + + assertThat(qsPanel.tileLayout!!.minRows).isEqualTo(1) + assertThat(qsPanel.tileLayout!!.maxColumns).isEqualTo(4) + + qsPanel.setColumnRowLayout(/* withMedia= */ true) + + assertThat(qsPanel.tileLayout!!.minRows).isEqualTo(2) + assertThat(qsPanel.tileLayout!!.maxColumns).isEqualTo(2) + } + private infix fun View.isLeftOf(other: View): Boolean { val rect = Rect() getBoundsOnScreen(rect) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 1eb0a51bcaf6..fee4b534d8dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -25,6 +25,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.haptics.qs.QSLongPressEffect +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.plugins.qs.QSTile @@ -62,6 +63,10 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var tileLayout: TileLayout @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener> @Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect> + @Mock private lateinit var mediaCarouselInteractor: MediaCarouselInteractor + + private val usingMediaPlayer: Boolean + get() = false private val uiEventLogger = UiEventLoggerFake() private val dumpManager = DumpManager() @@ -86,7 +91,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { quickQSPanel, qsHost, qsCustomizerController, - /* usingMediaPlayer = */ false, + usingMediaPlayer, mediaHost, { usingCollapsedLandscapeMedia }, metricsLogger, @@ -94,6 +99,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { qsLogger, dumpManager, longPressEffectProvider, + mediaCarouselInteractor, ) controller.init() @@ -163,6 +169,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { qsLogger: QSLogger, dumpManager: DumpManager, longPressEffectProvider: Provider<QSLongPressEffect>, + mediaCarouselInteractor: MediaCarouselInteractor, ) : QuickQSPanelController( view, @@ -177,6 +184,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { dumpManager, ResourcesSplitShadeStateController(), longPressEffectProvider, + mediaCarouselInteractor ) { private var rotation = RotationUtils.ROTATION_NONE diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt new file mode 100644 index 000000000000..b0aa6ddf44ce --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt @@ -0,0 +1,130 @@ +/* + * 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 + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.UserInfo +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository +import com.android.systemui.qs.panels.data.repository.qsPreferencesRepository +import com.android.systemui.settings.userFileManager +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.user.data.repository.userRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class QSPreferencesRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val underTest = with(kosmos) { qsPreferencesRepository } + + @Test + fun showLabels_updatesFromSharedPreferences() = + with(kosmos) { + testScope.runTest { + val latest by collectLastValue(underTest.showLabels) + assertThat(latest).isFalse() + + setShowLabelsInSharedPreferences(true) + assertThat(latest).isTrue() + + setShowLabelsInSharedPreferences(false) + assertThat(latest).isFalse() + } + } + + @Test + fun showLabels_updatesFromUserChange() = + with(kosmos) { + testScope.runTest { + fakeUserRepository.setUserInfos(USERS) + val latest by collectLastValue(underTest.showLabels) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + setShowLabelsInSharedPreferences(false) + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + setShowLabelsInSharedPreferences(true) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + assertThat(latest).isFalse() + } + } + + @Test + fun setShowLabels_inSharedPreferences() { + underTest.setShowLabels(false) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + + underTest.setShowLabels(true) + assertThat(getShowLabelsFromSharedPreferences(false)).isTrue() + } + + @Test + fun setShowLabels_forDifferentUser() = + with(kosmos) { + testScope.runTest { + fakeUserRepository.setUserInfos(USERS) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + underTest.setShowLabels(false) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + underTest.setShowLabels(true) + assertThat(getShowLabelsFromSharedPreferences(false)).isTrue() + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + } + } + + private fun getSharedPreferences(): SharedPreferences = + with(kosmos) { + return userFileManager.getSharedPreferences( + QSPreferencesRepository.FILE_NAME, + Context.MODE_PRIVATE, + userRepository.getSelectedUserInfo().id, + ) + } + + private fun setShowLabelsInSharedPreferences(value: Boolean) { + getSharedPreferences().edit().putBoolean(ICON_LABELS_KEY, value).apply() + } + + private fun getShowLabelsFromSharedPreferences(defaultValue: Boolean): Boolean { + return getSharedPreferences().getBoolean(ICON_LABELS_KEY, defaultValue) + } + + companion object { + private const val ICON_LABELS_KEY = "show_icon_labels" + private const val PRIMARY_USER_ID = 0 + private val PRIMARY_USER = UserInfo(PRIMARY_USER_ID, "user 0", UserInfo.FLAG_MAIN) + private const val ANOTHER_USER_ID = 1 + private val ANOTHER_USER = UserInfo(ANOTHER_USER_ID, "user 1", UserInfo.FLAG_FULL) + private val USERS = listOf(PRIMARY_USER, ANOTHER_USER) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt index 2da4b7296c35..87031d93db15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt @@ -31,9 +31,6 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -46,22 +43,21 @@ import org.junit.runner.RunWith class GridConsistencyInteractorTest : SysuiTestCase() { private val iconOnlyTiles = - MutableStateFlow( - setOf( - TileSpec.create("smallA"), - TileSpec.create("smallB"), - TileSpec.create("smallC"), - TileSpec.create("smallD"), - TileSpec.create("smallE"), - ) + setOf( + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), ) private val kosmos = testKosmos().apply { iconTilesRepository = object : IconTilesRepository { - override val iconTilesSpecs: StateFlow<Set<TileSpec>> - get() = iconOnlyTiles.asStateFlow() + override fun isIconTile(spec: TileSpec): Boolean { + return iconOnlyTiles.contains(spec) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt new file mode 100644 index 000000000000..9b08432e290f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt @@ -0,0 +1,90 @@ +/* + * 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 android.content.pm.UserInfo +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class IconLabelVisibilityInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val underTest = with(kosmos) { iconLabelVisibilityInteractor } + + @Before + fun setUp() { + with(kosmos) { fakeUserRepository.setUserInfos(USERS) } + } + + @Test + fun changingShowLabels_receivesCorrectShowLabels() = + with(kosmos) { + testScope.runTest { + val showLabels by collectLastValue(underTest.showLabels) + + underTest.setShowLabels(false) + runCurrent() + assertThat(showLabels).isFalse() + + underTest.setShowLabels(true) + runCurrent() + assertThat(showLabels).isTrue() + } + } + + @Test + fun changingUser_receivesCorrectShowLabels() = + with(kosmos) { + testScope.runTest { + val showLabels by collectLastValue(underTest.showLabels) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + underTest.setShowLabels(false) + runCurrent() + assertThat(showLabels).isFalse() + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + underTest.setShowLabels(true) + runCurrent() + assertThat(showLabels).isTrue() + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + runCurrent() + assertThat(showLabels).isFalse() + } + } + + companion object { + private val PRIMARY_USER = UserInfo(0, "user 0", UserInfo.FLAG_MAIN) + private val ANOTHER_USER = UserInfo(1, "user 1", UserInfo.FLAG_FULL) + private val USERS = listOf(PRIMARY_USER, ANOTHER_USER) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt index bda48adbfcc3..1eb6d63c5a39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt @@ -25,9 +25,6 @@ import com.android.systemui.qs.panels.data.repository.iconTilesRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -37,21 +34,20 @@ import org.junit.runner.RunWith class InfiniteGridConsistencyInteractorTest : SysuiTestCase() { private val iconOnlyTiles = - MutableStateFlow( - setOf( - TileSpec.create("smallA"), - TileSpec.create("smallB"), - TileSpec.create("smallC"), - TileSpec.create("smallD"), - TileSpec.create("smallE"), - ) + setOf( + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), ) private val kosmos = testKosmos().apply { iconTilesRepository = object : IconTilesRepository { - override val iconTilesSpecs: StateFlow<Set<TileSpec>> - get() = iconOnlyTiles.asStateFlow() + override fun isIconTile(spec: TileSpec): Boolean { + return iconOnlyTiles.contains(spec) + } } } private val underTest = with(kosmos) { infiniteGridConsistencyInteractor } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt index 0ec8552e8595..42b81de92af7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt @@ -116,6 +116,7 @@ class QSTileViewModelImplTest : SysuiTestCase() { "test_spec:\n" + " QSTileState(" + "icon=() -> com.android.systemui.common.shared.model.Icon?, " + + "iconRes=null, " + "label=test_data, " + "activationState=INACTIVE, " + "secondaryLabel=null, " + diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index 9798562ab5a8..29487cdace2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -1116,6 +1116,34 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse(); } + @Test + public void hasActiveSubIdOnDds_activeDdsAndIsOnlyNonTerrestrialNetwork_returnFalse() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(info.isEmbedded()).thenReturn(true); + when(info.isOnlyNonTerrestrialNetwork()).thenReturn(true); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); + + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertFalse(mInternetDialogController.hasActiveSubIdOnDds()); + } + + @Test + public void hasActiveSubIdOnDds_activeDdsAndIsNotOnlyNonTerrestrialNetwork_returnTrue() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(info.isEmbedded()).thenReturn(true); + when(info.isOnlyNonTerrestrialNetwork()).thenReturn(false); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); + + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertTrue(mInternetDialogController.hasActiveSubIdOnDds()); + } + private String getResourcesString(String name) { return mContext.getResources().getString(getResourcesId(name)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt index 4215b8c9a1a3..e7bde681fe6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt @@ -109,15 +109,10 @@ class WorkModeTileMapperTest : SysuiTestCase() { activationState: QSTileState.ActivationState, ): QSTileState { val label = testLabel + val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status return QSTileState( - icon = { - Icon.Loaded( - context.getDrawable( - com.android.internal.R.drawable.stat_sys_managed_profile_status - )!!, - null - ) - }, + icon = { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes = iconRes, label = label, activationState = activationState, secondaryLabel = diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index deecc5bb5a03..0d7a9e4d2430 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -146,7 +146,8 @@ public class RecordingServiceTest extends SysuiTestCase { @Test public void testLogStartPartialRecording() { - MediaProjectionCaptureTarget target = new MediaProjectionCaptureTarget(new LaunchCookie()); + MediaProjectionCaptureTarget target = + new MediaProjectionCaptureTarget(new LaunchCookie(), 12345); Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, target); mRecordingService.onStartCommand(startIntent, 0, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt index 896c3bf7547e..6f5c56eb9148 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt @@ -21,41 +21,38 @@ import android.net.Uri import android.os.Process import android.os.UserHandle import android.testing.AndroidTestingRunner -import android.view.accessibility.AccessibilityManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase -import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel -import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import java.util.UUID import kotlin.test.Test import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.runner.RunWith import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock import org.mockito.kotlin.never +import org.mockito.kotlin.times import org.mockito.kotlin.verify @RunWith(AndroidTestingRunner::class) @SmallTest class DefaultScreenshotActionsProviderTest : SysuiTestCase() { private val actionExecutor = mock<ActionExecutor>() - private val accessibilityManager = mock<AccessibilityManager>() private val uiEventLogger = mock<UiEventLogger>() + private val actionsCallback = mock<ScreenshotActionsController.ActionsCallback>() private val request = ScreenshotData.forTesting() private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0) - private lateinit var viewModel: ScreenshotViewModel private lateinit var actionsProvider: ScreenshotActionsProvider @Before fun setUp() { - viewModel = ScreenshotViewModel(accessibilityManager) request.userHandle = UserHandle.OWNER } @@ -63,8 +60,9 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() { actionsProvider = createActionsProvider() - assertNotNull(viewModel.previewAction.value) - viewModel.previewAction.value!!.invoke() + val previewActionCaptor = argumentCaptor<() -> Unit>() + verify(actionsCallback).providePreviewAction(previewActionCaptor.capture()) + previewActionCaptor.firstValue.invoke() verifyNoMoreInteractions(actionExecutor) } @@ -72,13 +70,13 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { fun actionButtonsAccessed_beforeScreenshotCompleted_doesNothing() { actionsProvider = createActionsProvider() - assertThat(viewModel.actions.value.size).isEqualTo(2) - val firstAction = viewModel.actions.value[0] - assertThat(firstAction.onClicked).isNotNull() - val secondAction = viewModel.actions.value[1] - assertThat(secondAction.onClicked).isNotNull() - firstAction.onClicked!!.invoke() - secondAction.onClicked!!.invoke() + val actionButtonCaptor = argumentCaptor<() -> Unit>() + verify(actionsCallback, times(2)) + .provideActionButton(any(), any(), actionButtonCaptor.capture()) + val firstAction = actionButtonCaptor.firstValue + val secondAction = actionButtonCaptor.secondValue + firstAction.invoke() + secondAction.invoke() verifyNoMoreInteractions(actionExecutor) } @@ -87,29 +85,39 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { actionsProvider = createActionsProvider() actionsProvider.setCompletedScreenshot(validResult) - viewModel.actions.value[0].onClicked!!.invoke() - verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq("")) + val actionButtonCaptor = argumentCaptor<() -> Unit>() + verify(actionsCallback, times(2)) + .provideActionButton(any(), any(), actionButtonCaptor.capture()) + actionButtonCaptor.firstValue.invoke() + + verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq("")) val intentCaptor = argumentCaptor<Intent>() verify(actionExecutor) - .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(true)) - assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT) + .startSharedTransition(intentCaptor.capture(), eq(Process.myUserHandle()), eq(false)) + assertThat(intentCaptor.firstValue.action).isEqualTo(Intent.ACTION_CHOOSER) } @Test fun actionAccessed_whilePending_launchesMostRecentAction() = runTest { actionsProvider = createActionsProvider() - viewModel.actions.value[0].onClicked!!.invoke() - viewModel.previewAction.value!!.invoke() - viewModel.actions.value[1].onClicked!!.invoke() + val previewActionCaptor = argumentCaptor<() -> Unit>() + verify(actionsCallback).providePreviewAction(previewActionCaptor.capture()) + val actionButtonCaptor = argumentCaptor<() -> Unit>() + verify(actionsCallback, times(2)) + .provideActionButton(any(), any(), actionButtonCaptor.capture()) + + actionButtonCaptor.firstValue.invoke() + previewActionCaptor.firstValue.invoke() + actionButtonCaptor.secondValue.invoke() actionsProvider.setCompletedScreenshot(validResult) - verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq("")) + verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq("")) val intentCaptor = argumentCaptor<Intent>() verify(actionExecutor) - .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(false)) - assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER) + .startSharedTransition(intentCaptor.capture(), eq(Process.myUserHandle()), eq(true)) + assertThat(intentCaptor.firstValue.action).isEqualTo(Intent.ACTION_EDIT) } @Test @@ -117,9 +125,12 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { actionsProvider = createActionsProvider() val onScrollClick = mock<Runnable>() - val numActions = viewModel.actions.value.size actionsProvider.onScrollChipReady(onScrollClick) - viewModel.actions.value[numActions].onClicked!!.invoke() + val actionButtonCaptor = argumentCaptor<() -> Unit>() + // share, edit, scroll + verify(actionsCallback, times(3)) + .provideActionButton(any(), any(), actionButtonCaptor.capture()) + actionButtonCaptor.thirdValue.invoke() verify(onScrollClick).run() } @@ -129,10 +140,13 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { actionsProvider = createActionsProvider() val onScrollClick = mock<Runnable>() - val numActions = viewModel.actions.value.size actionsProvider.onScrollChipReady(onScrollClick) + val actionButtonCaptor = argumentCaptor<() -> Unit>() actionsProvider.onScrollChipInvalidated() - viewModel.actions.value[numActions].onClicked!!.invoke() + // share, edit, scroll + verify(actionsCallback, times(3)) + .provideActionButton(any(), any(), actionButtonCaptor.capture()) + actionButtonCaptor.thirdValue.invoke() verify(onScrollClick, never()).run() } @@ -143,11 +157,15 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { val onScrollClick = mock<Runnable>() val onScrollClick2 = mock<Runnable>() - val numActions = viewModel.actions.value.size + actionsProvider.onScrollChipReady(onScrollClick) actionsProvider.onScrollChipInvalidated() actionsProvider.onScrollChipReady(onScrollClick2) - viewModel.actions.value[numActions].onClicked!!.invoke() + val actionButtonCaptor = argumentCaptor<() -> Unit>() + // share, edit, scroll + verify(actionsCallback, times(3)) + .provideActionButton(any(), any(), actionButtonCaptor.capture()) + actionButtonCaptor.thirdValue.invoke() verify(onScrollClick2).run() verify(onScrollClick, never()).run() @@ -156,11 +174,11 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { private fun createActionsProvider(): ScreenshotActionsProvider { return DefaultScreenshotActionsProvider( context, - viewModel, uiEventLogger, + UUID.randomUUID(), request, - "testid", - actionExecutor + actionExecutor, + actionsCallback, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt new file mode 100644 index 000000000000..2a3c31aee6e7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel +import java.util.UUID +import kotlin.test.Test +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ScreenshotActionsControllerTest : SysuiTestCase() { + private val screenshotData = mock<ScreenshotData>() + private val actionExecutor = mock<ActionExecutor>() + private val viewModel = mock<ScreenshotViewModel>() + private val onClick = mock<() -> Unit>() + + private lateinit var actionsController: ScreenshotActionsController + private lateinit var fakeActionsProvider1: FakeActionsProvider + private lateinit var fakeActionsProvider2: FakeActionsProvider + private val actionsProviderFactory = + object : ScreenshotActionsProvider.Factory { + var isFirstCall = true + override fun create( + requestId: UUID, + request: ScreenshotData, + actionExecutor: ActionExecutor, + actionsCallback: ScreenshotActionsController.ActionsCallback + ): ScreenshotActionsProvider { + return if (isFirstCall) { + isFirstCall = false + fakeActionsProvider1 = FakeActionsProvider(actionsCallback) + fakeActionsProvider1 + } else { + fakeActionsProvider2 = FakeActionsProvider(actionsCallback) + fakeActionsProvider2 + } + } + } + + @Before + fun setUp() { + actionsController = + ScreenshotActionsController(viewModel, actionsProviderFactory, actionExecutor) + } + + @Test + fun setPreview_onCurrentScreenshot_updatesViewModel() { + actionsController.setCurrentScreenshot(screenshotData) + fakeActionsProvider1.callPreview(onClick) + + verify(viewModel).setPreviewAction(onClick) + } + + @Test + fun setPreview_onNonCurrentScreenshot_doesNotUpdateViewModel() { + actionsController.setCurrentScreenshot(screenshotData) + actionsController.setCurrentScreenshot(screenshotData) + fakeActionsProvider1.callPreview(onClick) + + verify(viewModel, never()).setPreviewAction(any()) + } + + class FakeActionsProvider( + private val actionsCallback: ScreenshotActionsController.ActionsCallback + ) : ScreenshotActionsProvider { + + fun callPreview(onClick: () -> Unit) { + actionsCallback.providePreviewAction(onClick) + } + + override fun onScrollChipReady(onClick: Runnable) {} + + override fun onScrollChipInvalidated() {} + + override fun setCompletedScreenshot(result: ScreenshotSavedResult) {} + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 49a467e152ec..b8267a0e83d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -18,6 +18,8 @@ package com.android.systemui.shade import android.graphics.Rect import android.os.PowerManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils @@ -30,13 +32,14 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE import com.android.systemui.SysuiTestCase 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.fakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository +import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes @@ -51,6 +54,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat @@ -64,9 +68,11 @@ import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyFloat import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -92,14 +98,14 @@ 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 fun setUp() { MockitoAnnotations.initMocks(this) - communalRepository = kosmos.fakeCommunalRepository + communalRepository = kosmos.fakeCommunalSceneRepository ambientTouchComponentFactory = object : AmbientTouchComponent.Factory { @@ -124,6 +130,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController ) } testableLooper = TestableLooper.get(this) @@ -166,6 +173,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController ) // First call succeeds. @@ -176,6 +184,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalClosed_doesNotIntercept() = with(kosmos) { @@ -187,6 +196,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_openGesture_interceptsTouches() = with(kosmos) { @@ -204,6 +214,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalTransitioning_interceptsTouches() = with(kosmos) { @@ -230,6 +241,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalOpen_interceptsTouches() = with(kosmos) { @@ -244,6 +256,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() = with(kosmos) { @@ -262,6 +275,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() = with(kosmos) { @@ -278,6 +292,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_containerViewDisposed_doesNotIntercept() = with(kosmos) { @@ -310,6 +325,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController, ) assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED) @@ -329,6 +345,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController, ) // Only initView without attaching a view as we don't want the flows to start collecting @@ -499,13 +516,30 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) + fun fullScreenSwipeGesture_doNotProcessTouchesInNotificationStack() = + with(kosmos) { + testScope.runTest { + // Communal is closed. + goToScene(CommunalScenes.Blank) + `when`( + notificationStackScrollLayoutController.isBelowLastNotification( + anyFloat(), + anyFloat() + ) + ) + .thenReturn(false) + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() + } + } + private fun initAndAttachContainerView() { containerView = View(context) parentView = FrameLayout(context) - parentView.addView(containerView) - underTest.initView(containerView) + parentView.addView(underTest.initView(containerView)) // Attach the view so that flows start collecting. ViewUtils.attachView(parentView, CONTAINER_WIDTH, CONTAINER_HEIGHT) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 041adea8decc..c3cedf84a864 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -837,6 +837,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mJavaAdapter, mCastController, new ResourcesSplitShadeStateController(), + () -> mKosmos.getCommunalTransitionViewModel(), () -> mLargeScreenHeaderHelper ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index 845744a54791..85541aa8abda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -308,6 +308,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { new JavaAdapter(mTestScope.getBackgroundScope()), mCastController, splitShadeStateController, + () -> mKosmos.getCommunalTransitionViewModel(), () -> mLargeScreenHeaderHelper ); mQsController.init(); 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/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java index 22c9e45d48af..6985a27a59c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java @@ -20,14 +20,21 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.drawable.Icon; +import android.os.Handler; +import android.platform.test.annotations.EnableFlags; +import android.view.KeyboardShortcutGroup; +import android.view.KeyboardShortcutInfo; import android.view.WindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.google.android.material.bottomsheet.BottomSheetDialog; @@ -36,10 +43,14 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Arrays; +import java.util.Collections; + @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutListSearchTest extends SysuiTestCase { @@ -51,6 +62,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { @Mock private BottomSheetDialog mBottomSheetDialog; @Mock WindowManager mWindowManager; + @Mock Handler mHandler; @Before public void setUp() { @@ -58,6 +70,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch; mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog; mKeyboardShortcutListSearch.mContext = mContext; + mKeyboardShortcutListSearch.mBackgroundHandler = mHandler; } @Test @@ -78,4 +91,59 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt()); verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt()); } + + @Test + @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) + public void requestAppKeyboardShortcuts_callback_sanitisesIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID); + + ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = + ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); + ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWindowManager).requestAppKeyboardShortcuts(callbackCaptor.capture(), anyInt()); + callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); + verify(mHandler).post(handlerRunnableCaptor.capture()); + handlerRunnableCaptor.getValue().run(); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + } + + @Test + @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) + public void requestImeKeyboardShortcuts_callback_sanitisesIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID); + + ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = + ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); + ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWindowManager).requestImeKeyboardShortcuts(callbackCaptor.capture(), anyInt()); + callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); + verify(mHandler).post(handlerRunnableCaptor.capture()); + handlerRunnableCaptor.getValue().run(); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + + } + + private KeyboardShortcutGroup createKeyboardShortcutGroupForIconTests() { + Icon icon = mock(Icon.class); + + KeyboardShortcutInfo info1 = mock(KeyboardShortcutInfo.class); + KeyboardShortcutInfo info2 = mock(KeyboardShortcutInfo.class); + when(info1.getIcon()).thenReturn(icon); + when(info2.getIcon()).thenReturn(icon); + when(info1.getLabel()).thenReturn("label"); + when(info2.getLabel()).thenReturn("label"); + + KeyboardShortcutGroup group = new KeyboardShortcutGroup("label", + Arrays.asList(new KeyboardShortcutInfo[]{ info1, info2})); + group.setPackageName("com.example"); + return group; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java index a3ecde0fe976..2b3f13986113 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java @@ -20,25 +20,36 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Dialog; +import android.graphics.drawable.Icon; +import android.os.Handler; +import android.platform.test.annotations.EnableFlags; +import android.view.KeyboardShortcutGroup; +import android.view.KeyboardShortcutInfo; import android.view.WindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Arrays; +import java.util.Collections; + @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutsTest extends SysuiTestCase { @@ -50,6 +61,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase { @Mock private Dialog mDialog; @Mock WindowManager mWindowManager; + @Mock Handler mHandler; @Before public void setUp() { @@ -57,6 +69,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase { mKeyboardShortcuts.sInstance = mKeyboardShortcuts; mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog; mKeyboardShortcuts.mContext = mContext; + mKeyboardShortcuts.mBackgroundHandler = mHandler; } @Test @@ -77,4 +90,78 @@ public class KeyboardShortcutsTest extends SysuiTestCase { verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt()); verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt()); } + + @Test + public void sanitiseShortcuts_clearsIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + KeyboardShortcuts.sanitiseShortcuts(Collections.singletonList(group)); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + } + + @Test + public void sanitiseShortcuts_nullPackage_clearsIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + group.setPackageName(null); + + KeyboardShortcuts.sanitiseShortcuts(Collections.singletonList(group)); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + } + + @Test + @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) + public void requestAppKeyboardShortcuts_callback_sanitisesIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + mKeyboardShortcuts.toggle(mContext, DEVICE_ID); + + ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = + ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); + ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWindowManager).requestAppKeyboardShortcuts(callbackCaptor.capture(), anyInt()); + callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); + verify(mHandler).post(handlerRunnableCaptor.capture()); + handlerRunnableCaptor.getValue().run(); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + } + + @Test + @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) + public void requestImeKeyboardShortcuts_callback_sanitisesIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + mKeyboardShortcuts.toggle(mContext, DEVICE_ID); + + ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = + ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); + ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWindowManager).requestImeKeyboardShortcuts(callbackCaptor.capture(), anyInt()); + callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); + verify(mHandler).post(handlerRunnableCaptor.capture()); + handlerRunnableCaptor.getValue().run(); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + + } + + private KeyboardShortcutGroup createKeyboardShortcutGroupForIconTests() { + Icon icon = mock(Icon.class); + + KeyboardShortcutInfo info1 = mock(KeyboardShortcutInfo.class); + KeyboardShortcutInfo info2 = mock(KeyboardShortcutInfo.class); + when(info1.getIcon()).thenReturn(icon); + when(info2.getIcon()).thenReturn(icon); + + KeyboardShortcutGroup group = new KeyboardShortcutGroup("label", + Arrays.asList(new KeyboardShortcutInfo[]{ info1, info2})); + group.setPackageName("com.example"); + return group; + } } 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/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt new file mode 100644 index 000000000000..0f33b9dfc077 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.chips.mediaprojection.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.mediaprojection.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask +import com.android.systemui.res.R +import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel +import com.android.systemui.statusbar.chips.ui.viewmodel.mediaProjectionChipInteractor +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 MediaProjectionChipInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository + private val systemClock = kosmos.fakeSystemClock + + private val underTest = kosmos.mediaProjectionChipInteractor + + @Test + fun chip_notProjectingState_isHidden() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + } + + @Test + fun chip_singleTaskState_isShownWithIcon() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.SingleTask(createTask(taskId = 1)) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + val icon = (latest as OngoingActivityChipModel.Shown).icon + assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected) + } + + @Test + fun chip_entireScreenState_isShownWithIcon() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.EntireScreen + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + val icon = (latest as OngoingActivityChipModel.Shown).icon + assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected) + } + + @Test + fun chip_timeResetsOnEachNewShare() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + systemClock.setElapsedRealtime(1234) + mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.EntireScreen + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234) + + mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + + systemClock.setElapsedRealtime(5678) + mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.EntireScreen + + 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/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..121229c321b6 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 @@ -23,8 +23,13 @@ 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.mediaprojection.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask 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 @@ -33,13 +38,20 @@ import org.junit.Test class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val kosmos = Kosmos() + private val testScope = kosmos.testScope + + private val screenRecordState = kosmos.screenRecordRepository.screenRecordState + private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState + private val callState = kosmos.callChipInteractor.chip + private val underTest = kosmos.ongoingActivityChipsViewModel @Test fun chip_allHidden_hidden() = - kosmos.testScope.runTest { - kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden - kosmos.callChipInteractor.chip.value = OngoingActivityChipModel.Hidden + testScope.runTest { + screenRecordState.value = ScreenRecordModel.DoingNothing + mediaProjectionState.value = MediaProjectionState.NotProjecting + callState.value = OngoingActivityChipModel.Hidden val latest by collectLastValue(underTest.chip) @@ -48,53 +60,74 @@ 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.callChipInteractor.chip.value = OngoingActivityChipModel.Hidden + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionState.value = MediaProjectionState.NotProjecting + callState.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 = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Recording + + val callChip = OngoingActivityChipModel.Shown( - Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")), - startTimeMs = 500L, + Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")), + startTimeMs = 600L, ) {} - kosmos.screenRecordChipInteractor.chip.value = screenRecordChip + callState.value = callChip + + val latest by collectLastValue(underTest.chip) + + assertIsScreenRecordChip(latest) + } + + @Test + fun chip_screenRecordShowAndMediaProjectionShow_screenRecordShown() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionState.value = MediaProjectionState.EntireScreen + callState.value = OngoingActivityChipModel.Hidden + + val latest by collectLastValue(underTest.chip) + assertIsScreenRecordChip(latest) + } + + @Test + fun chip_mediaProjectionShowAndCallShow_mediaProjectionShown() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.DoingNothing + mediaProjectionState.value = MediaProjectionState.EntireScreen val callChip = OngoingActivityChipModel.Shown( Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")), startTimeMs = 600L, ) {} - kosmos.callChipInteractor.chip.value = callChip + callState.value = callChip val latest by collectLastValue(underTest.chip) - assertThat(latest).isEqualTo(screenRecordChip) + assertIsMediaProjectionChip(latest) } @Test - fun chip_screenRecordHideAndCallShown_callShown() = - kosmos.testScope.runTest { - kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden + fun chip_screenRecordAndMediaProjectionHideAndCallShown_callShown() = + testScope.runTest { + screenRecordState.value = ScreenRecordModel.DoingNothing + mediaProjectionState.value = MediaProjectionState.NotProjecting val callChip = OngoingActivityChipModel.Shown( Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")), startTimeMs = 600L, ) {} - kosmos.callChipInteractor.chip.value = callChip + callState.value = callChip val latest by collectLastValue(underTest.chip) @@ -103,58 +136,77 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun chip_higherPriorityChipAdded_lowerPriorityChipReplaced() = - kosmos.testScope.runTest { + testScope.runTest { // Start with just the lower priority call chip val callChip = OngoingActivityChipModel.Shown( Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")), startTimeMs = 600L, ) {} - kosmos.callChipInteractor.chip.value = callChip - kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden + callState.value = callChip + mediaProjectionState.value = MediaProjectionState.NotProjecting + screenRecordState.value = ScreenRecordModel.DoingNothing val latest by collectLastValue(underTest.chip) assertThat(latest).isEqualTo(callChip) + // WHEN the higher priority media projection chip is added + mediaProjectionState.value = MediaProjectionState.SingleTask(createTask(taskId = 1)) + + // THEN the higher priority media projection chip is used + assertIsMediaProjectionChip(latest) + // 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 + 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 + testScope.runTest { + // WHEN all chips are active + screenRecordState.value = ScreenRecordModel.Recording + mediaProjectionState.value = MediaProjectionState.EntireScreen val callChip = OngoingActivityChipModel.Shown( Icon.Resource(R.drawable.ic_call, ContentDescription.Loaded("icon")), startTimeMs = 600L, ) {} - kosmos.callChipInteractor.chip.value = callChip + callState.value = callChip val latest by collectLastValue(underTest.chip) - assertThat(latest).isEqualTo(screenRecordChip) + // THEN the highest priority screen record is used + assertIsScreenRecordChip(latest) // WHEN the higher priority screen record is removed - kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden + screenRecordState.value = ScreenRecordModel.DoingNothing + + // THEN the lower priority media projection is used + assertIsMediaProjectionChip(latest) + + // WHEN the higher priority media projection is removed + mediaProjectionState.value = MediaProjectionState.NotProjecting // THEN the lower priority call is used assertThat(latest).isEqualTo(callChip) } + + companion object { + 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) + } + + fun assertIsMediaProjectionChip(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.ic_cast_connected) + } + } } 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..e984200c305e 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) ) } @@ -89,7 +91,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -108,7 +111,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldNotHeadsUp( @@ -127,7 +131,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -144,7 +149,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -161,7 +167,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -178,7 +185,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -195,7 +203,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { assertFsiNotSuppressed() } @@ -206,7 +215,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -230,7 +240,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro ).thenReturn(PERMISSION_GRANTED) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( 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/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index f461e2f67d20..12f3ef3cf553 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -171,7 +171,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // and then we would test both configurations, but currently they are all read // in the constructor. mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION); - mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); // Inject dependencies before initializing the layout mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); 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..cb9790b2495a 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 @@ -138,6 +138,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.CameraLauncher; +import com.android.systemui.shade.GlanceableHubContainerController; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -172,7 +173,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 +189,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 +196,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; @@ -341,6 +338,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private KeyboardShortcuts mKeyboardShortcuts; @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch; @Mock private PackageManager mPackageManager; + @Mock private GlanceableHubContainerController mGlanceableHubContainerController; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); @@ -374,6 +372,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 +395,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, mAvalancheProvider, mSystemSettings, - mPackageManager); + mPackageManager, + Optional.of(mBubbles)); mVisualInterruptionDecisionProvider.start(); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); @@ -591,7 +592,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, () -> mFingerprintManager, mActivityStarter, - mBrightnessMirrorShowingInteractor + mBrightnessMirrorShowingInteractor, + mGlanceableHubContainerController ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); mCentralSurfaces.initShadeVisibilityListener(); @@ -1418,46 +1420,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/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt index 87d813c6d19f..e0f1e1a46d3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt @@ -63,7 +63,7 @@ class CollapsedStatusBarFragmentLoggerTest : SysuiTestCase() { StatusBarVisibilityModel( showClock = false, showNotificationIcons = true, - showOngoingCallChip = false, + showOngoingActivityChip = false, showSystemInfo = true, ) ) @@ -74,7 +74,7 @@ class CollapsedStatusBarFragmentLoggerTest : SysuiTestCase() { assertThat(actualString).contains("showClock=false") assertThat(actualString).contains("showNotificationIcons=true") - assertThat(actualString).contains("showOngoingCallChip=false") + assertThat(actualString).contains("showOngoingActivityChip=false") assertThat(actualString).contains("showSystemInfo=true") } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index ff182ad4a5ea..ee27cea48565 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone.fragment; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; @@ -33,6 +34,8 @@ import android.app.StatusBarManager; import android.content.Context; import android.os.Bundle; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -46,7 +49,6 @@ import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogcatEchoTracker; import com.android.systemui.plugins.DarkIconDispatcher; @@ -56,7 +58,6 @@ import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; -import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; @@ -92,11 +93,9 @@ import java.util.List; @RunWithLooper(setAsMainLooper = true) @SmallTest public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { - private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(); private NotificationIconAreaController mMockNotificationAreaController; private ShadeExpansionStateManager mShadeExpansionStateManager; private OngoingCallController mOngoingCallController; - private OngoingActivityChipsViewModel mOngoingActivityChipsViewModel; private SystemStatusAnimationScheduler mAnimationScheduler; private StatusBarLocationPublisher mLocationPublisher; // Set in instantiate() @@ -421,6 +420,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) public void disable_noOngoingCall_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -433,6 +433,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -446,6 +447,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -459,6 +461,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) public void disable_hasOngoingCallButAlsoHun_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -472,6 +475,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) public void disable_ongoingCallEnded_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -498,8 +502,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + // Enable animations for testing so that we can verify we still aren't animating + fragment.enableAnimationsForTesting(); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); // Ongoing call started @@ -512,6 +519,161 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + public void screenSharingChipsDisabled_ignoresNewCallback() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + // WHEN there *is* an ongoing call via old callback + when(mOngoingCallController.hasOngoingCall()).thenReturn(true); + fragment.disable(DEFAULT_DISPLAY, 0, 0, true); + + // WHEN there's *no* ongoing activity via new callback + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ false); + + // THEN the old callback value is used, so the view is shown + assertEquals(View.VISIBLE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + + // WHEN there's *no* ongoing call via old callback + when(mOngoingCallController.hasOngoingCall()).thenReturn(false); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // WHEN there *is* an ongoing activity via new callback + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ true); + + // THEN the old callback value is used, so the view is hidden + assertEquals(View.GONE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + public void noOngoingActivity_chipHidden() { + resumeAndGetFragment(); + + // TODO(b/332662551): We *should* be able to just set a value on + // mCollapsedStatusBarViewModel.getOngoingActivityChip() instead of manually invoking the + // listener, but I'm unable to get the fragment to get attached so that the binder starts + // listening to flows. + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ false); + + assertEquals(View.GONE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + public void hasOngoingActivity_chipDisplayedAndNotificationIconsHidden() { + resumeAndGetFragment(); + + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ true); + + assertEquals(View.VISIBLE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + public void hasOngoingActivityButNotificationIconsDisabled_chipHidden() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ true); + + fragment.disable(DEFAULT_DISPLAY, + StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); + + assertEquals(View.GONE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + public void hasOngoingActivityButAlsoHun_chipHidden() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ true); + when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.GONE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + public void ongoingActivityEnded_chipHidden() { + resumeAndGetFragment(); + + // Ongoing activity started + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ true); + + assertEquals(View.VISIBLE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + + // Ongoing activity ended + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ false); + + assertEquals(View.GONE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + public void hasOngoingActivity_hidesNotifsWithoutAnimation() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + // Enable animations for testing so that we can verify we still aren't animating + fragment.enableAnimationsForTesting(); + + // Ongoing call started + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ true); + + // Notification area is hidden without delay + assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + public void screenSharingChipsEnabled_ignoresOngoingCallController() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + // WHEN there *is* an ongoing call via old callback + when(mOngoingCallController.hasOngoingCall()).thenReturn(true); + fragment.disable(DEFAULT_DISPLAY, 0, 0, true); + + // WHEN there's *no* ongoing activity via new callback + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ false); + + // THEN the new callback value is used, so the view is hidden + assertEquals(View.GONE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + + // WHEN there's *no* ongoing call via old callback + when(mOngoingCallController.hasOngoingCall()).thenReturn(false); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // WHEN there *is* an ongoing activity via new callback + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasOngoingActivity= */ true); + + // THEN the new callback value is used, so the view is shown + assertEquals(View.VISIBLE, + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); + } + + @Test public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); @@ -670,7 +832,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { MockitoAnnotations.initMocks(this); setUpDaggerComponent(); mOngoingCallController = mock(OngoingCallController.class); - mOngoingActivityChipsViewModel = mKosmos.getOngoingActivityChipsViewModel(); mAnimationScheduler = mock(SystemStatusAnimationScheduler.class); mLocationPublisher = mock(StatusBarLocationPublisher.class); mStatusBarIconController = mock(StatusBarIconController.class); @@ -691,7 +852,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { return new CollapsedStatusBarFragment( mStatusBarFragmentComponentFactory, mOngoingCallController, - mOngoingActivityChipsViewModel, mAnimationScheduler, mLocationPublisher, mMockNotificationAreaController, @@ -773,7 +933,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private CollapsedStatusBarFragment resumeAndGetFragment() { mFragments.dispatchResume(); processAllMessages(); - return (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + fragment.disableAnimationsForTesting(); + return fragment; } private View getUserChipView() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt index 8e789cb2cae6..022b5d295256 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt @@ -36,7 +36,7 @@ class StatusBarVisibilityModelTest : SysuiTestCase() { StatusBarVisibilityModel( showClock = true, showNotificationIcons = true, - showOngoingCallChip = true, + showOngoingActivityChip = true, showSystemInfo = true, ) @@ -72,17 +72,17 @@ class StatusBarVisibilityModelTest : SysuiTestCase() { } @Test - fun createModelFromFlags_ongoingCallChipNotDisabled_showOngoingCallChipTrue() { + fun createModelFromFlags_ongoingCallChipNotDisabled_showOngoingActivityChipTrue() { val result = createModelFromFlags(disabled1 = 0, disabled2 = 0) - assertThat(result.showOngoingCallChip).isTrue() + assertThat(result.showOngoingActivityChip).isTrue() } @Test - fun createModelFromFlags_ongoingCallChipDisabled_showOngoingCallChipFalse() { + fun createModelFromFlags_ongoingCallChipDisabled_showOngoingActivityChipFalse() { val result = createModelFromFlags(disabled1 = DISABLE_ONGOING_CALL_CHIP, disabled2 = 0) - assertThat(result.showOngoingCallChip).isFalse() + assertThat(result.showOngoingActivityChip).isFalse() } @Test 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..cdc4733715e5 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,14 +51,17 @@ 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(), + logBuffer = FakeLogBuffer.Factory.create(), + verboseLogBuffer = FakeLogBuffer.Factory.create(), systemClock, ) private val demoDataSource = 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..02f53b6846e8 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,9 +91,11 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { underTest = DeviceBasedSatelliteRepositoryImpl( Optional.empty(), + telephonyManager, dispatcher, testScope.backgroundScope, - FakeLogBuffer.Factory.create(), + logBuffer = FakeLogBuffer.Factory.create(), + verboseLogBuffer = FakeLogBuffer.Factory.create(), systemClock, ) @@ -362,6 +369,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,9 +449,11 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { underTest = DeviceBasedSatelliteRepositoryImpl( if (satMan != null) Optional.of(satMan) else Optional.empty(), + telephonyManager, dispatcher, testScope.backgroundScope, - FakeLogBuffer.Factory.create(), + logBuffer = FakeLogBuffer.Factory.create(), + verboseLogBuffer = FakeLogBuffer.Factory.create(), systemClock, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 606feab86d58..c9fe44918757 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -32,6 +32,14 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.log.assertLogsWtf +import com.android.systemui.mediaprojection.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository +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.OngoingActivityChipsViewModelTest.Companion.assertIsMediaProjectionChip +import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip +import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel import com.android.systemui.statusbar.data.model.StatusBarMode import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository @@ -65,6 +73,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { kosmos.lightsOutInteractor, kosmos.activeNotificationsInteractor, kosmos.keyguardTransitionInteractor, + kosmos.ongoingActivityChipsViewModel, kosmos.applicationCoroutineScope, ) @@ -382,6 +391,25 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { } } + @Test + fun ongoingActivityChip_matchesViewModel() = + testScope.runTest { + val latest by collectLastValue(underTest.ongoingActivityChip) + + kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording + + assertIsScreenRecordChip(latest) + + kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing + + assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden) + + kosmos.fakeMediaProjectionRepository.mediaProjectionState.value = + MediaProjectionState.EntireScreen + + assertIsMediaProjectionChip(latest) + } + private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) = ActiveNotificationsStore.Builder() .apply { notifications.forEach(::addIndividualNotif) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt index bc50f7967403..c3c9907bc891 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -27,6 +28,9 @@ class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel { override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>() + override val ongoingActivityChip: MutableStateFlow<OngoingActivityChipModel> = + MutableStateFlow(OngoingActivityChipModel.Hidden) + override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut fun setNotificationLightsOut(lightsOut: Boolean) { 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/android/graphics/drawable/TestStubDrawable.kt b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt index 1a9f4b40c179..430fb5985848 100644 --- a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt +++ b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt @@ -27,8 +27,11 @@ import android.graphics.PixelFormat class TestStubDrawable(private val name: String? = null) : Drawable() { override fun draw(canvas: Canvas) = Unit + override fun setAlpha(alpha: Int) = Unit + override fun setColorFilter(colorFilter: ColorFilter?) = Unit + override fun getOpacity(): Int = PixelFormat.UNKNOWN override fun toString(): String { @@ -38,6 +41,10 @@ class TestStubDrawable(private val name: String? = null) : Drawable() { override fun getConstantState(): ConstantState = TestStubConstantState(this, changingConfigurations) + override fun equals(other: Any?): Boolean { + return (other as? TestStubDrawable ?: return false).name == name + } + private class TestStubConstantState( private val drawable: Drawable, private val changingConfigurations: Int, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java index e470406499b6..a5819931549d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java @@ -19,6 +19,7 @@ import static org.mockito.Mockito.when; import android.app.Fragment; import android.app.Instrumentation; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.BaseFragmentTest; import android.testing.DexmakerShareClassLoaderRule; @@ -31,6 +32,7 @@ import com.android.systemui.utils.leaks.LeakCheckedTest.SysuiLeakCheck; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.mockito.Mockito; @@ -43,6 +45,12 @@ public abstract class SysuiBaseFragmentTest extends BaseFragmentTest { @Rule public final SysuiLeakCheck mLeakCheck = new SysuiLeakCheck(); + @ClassRule + public static final SetFlagsRule.ClassRule mSetFlagsClassRule = + new SetFlagsRule.ClassRule( + com.android.systemui.Flags.class); + @Rule public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule(); + @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt index 9765d531472c..53285eb715ba 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt @@ -23,7 +23,6 @@ import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module import javax.inject.Inject -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -40,7 +39,7 @@ class FakeDisplayStateRepository @Inject constructor() : DisplayStateRepository override val currentDisplaySize: StateFlow<Size> = _currentDisplaySize.asStateFlow() private val _isLargeScreen = MutableStateFlow<Boolean>(false) - override val isLargeScreen: Flow<Boolean> = _isLargeScreen.asStateFlow() + override val isLargeScreen: StateFlow<Boolean> = _isLargeScreen.asStateFlow() override val isReverseDefaultRotation = false @@ -55,6 +54,10 @@ class FakeDisplayStateRepository @Inject constructor() : DisplayStateRepository fun setCurrentDisplaySize(size: Size) { _currentDisplaySize.value = size } + + fun setIsLargeScreen(isLargeScreen: Boolean) { + _isLargeScreen.value = isLargeScreen + } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt index 7f9a71cd149e..56297f0d7f43 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt @@ -25,6 +25,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture val Kosmos.promptSelectorInteractor by Fixture { PromptSelectorInteractorImpl( fingerprintPropertyRepository = fingerprintPropertyRepository, + displayStateInteractor = displayStateInteractor, promptRepository = promptRepository, lockPatternUtils = lockPatternUtils ) 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/brightness/data/repository/FakeScreenBrightnessRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt index a05b5e65ce9d..ad5242e2e036 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt @@ -19,7 +19,7 @@ package com.android.systemui.brightness.data.repository import android.hardware.display.BrightnessInfo import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF -import com.android.systemui.brightness.data.model.LinearBrightness +import com.android.systemui.brightness.shared.model.LinearBrightness import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt index 22784e47d277..0e8427310895 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt @@ -18,6 +18,15 @@ package com.android.systemui.brightness.domain.interactor import com.android.systemui.brightness.data.repository.screenBrightnessRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.util.mockito.mock val Kosmos.screenBrightnessInteractor by - Kosmos.Fixture { ScreenBrightnessInteractor(screenBrightnessRepository) } + Kosmos.Fixture { + ScreenBrightnessInteractor( + screenBrightnessRepository, + applicationCoroutineScope, + mock<TableLogBuffer>(), + ) + } 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/CommunalSceneRepositoryKosmos.kt index 482d60ce8adf..a7a18a06aa8b 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/CommunalSceneRepositoryKosmos.kt @@ -20,8 +20,9 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope -val Kosmos.fakeCommunalRepository by Fixture { - FakeCommunalRepository(applicationScope = applicationCoroutineScope) +val Kosmos.fakeCommunalSceneRepository by Fixture { + FakeCommunalSceneRepository(applicationScope = applicationCoroutineScope) } -val Kosmos.communalRepository by Fixture<CommunalRepository> { fakeCommunalRepository } +val Kosmos.communalSceneRepository by + Fixture<CommunalSceneRepository> { fakeCommunalSceneRepository } 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/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt index a6b40df8e81b..fb12897ead19 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt @@ -23,12 +23,14 @@ import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection +import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.viewmodel.keyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.util.mockito.mock import java.util.Optional +import org.mockito.Mockito.spy val Kosmos.keyguardClockSection: ClockSection by Kosmos.Fixture { @@ -42,6 +44,9 @@ val Kosmos.keyguardClockSection: ClockSection by ) } +val Kosmos.keyguardSmartspaceSection: SmartspaceSection by + Kosmos.Fixture { mock<SmartspaceSection>() } + val Kosmos.defaultKeyguardBlueprint by Kosmos.Fixture { DefaultKeyguardBlueprint( @@ -57,7 +62,7 @@ val Kosmos.defaultKeyguardBlueprint by aodBurnInSection = mock(), communalTutorialIndicatorSection = mock(), clockSection = keyguardClockSection, - smartspaceSection = mock(), + smartspaceSection = keyguardSmartspaceSection, keyguardSliceViewSection = mock(), udfpsAccessibilityOverlaySection = mock(), accessibilityActionsSection = mock(), @@ -80,7 +85,7 @@ val Kosmos.splitShadeBlueprint by aodBurnInSection = mock(), communalTutorialIndicatorSection = mock(), clockSection = keyguardClockSection, - smartspaceSection = mock(), + smartspaceSection = keyguardSmartspaceSection, mediaSection = mock(), accessibilityActionsSection = mock(), ) @@ -88,13 +93,15 @@ val Kosmos.splitShadeBlueprint by val Kosmos.keyguardBlueprintRepository by Kosmos.Fixture { - KeyguardBlueprintRepository( - blueprints = - setOf( - defaultKeyguardBlueprint, - splitShadeBlueprint, - ), - handler = fakeExecutorHandler, - assert = mock(), + spy( + KeyguardBlueprintRepository( + blueprints = + setOf( + defaultKeyguardBlueprint, + splitShadeBlueprint, + ), + handler = fakeExecutorHandler, + assert = mock(), + ) ) } 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/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt index 5256ce4b9adf..4328ca153374 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt @@ -20,6 +20,8 @@ import android.content.applicationContext import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository +import com.android.systemui.keyguard.data.repository.keyguardClockSection +import com.android.systemui.keyguard.data.repository.keyguardSmartspaceSection import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -34,5 +36,7 @@ val Kosmos.keyguardBlueprintInteractor by clockInteractor = keyguardClockInteractor, configurationInteractor = configurationInteractor, fingerprintPropertyInteractor = fingerprintPropertyInteractor, + clockSection = keyguardClockSection, + smartspaceSection = keyguardSmartspaceSection, ) } 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/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt index 63b87c075378..0c538ff1d6fe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt @@ -16,8 +16,14 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.os.fakeExecutorHandler import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor import com.android.systemui.kosmos.Kosmos val Kosmos.keyguardBlueprintViewModel by - Kosmos.Fixture { KeyguardBlueprintViewModel(keyguardBlueprintInteractor) } + Kosmos.Fixture { + KeyguardBlueprintViewModel( + fakeExecutorHandler, + keyguardBlueprintInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index f856d2700270..2567ffee9be8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -32,7 +32,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.keyguardRootViewModel by Fixture { KeyguardRootViewModel( - scope = applicationCoroutineScope, + applicationScope = applicationCoroutineScope, deviceEntryInteractor = deviceEntryInteractor, dozeParameters = dozeParameters, keyguardInteractor = keyguardInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index f2f4332e482e..0b28e3f0a083 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -27,8 +27,9 @@ import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.classifier.falsingCollector import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.configurationInteractor -import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor @@ -86,7 +87,8 @@ class KosmosJavaAdapter() { val configurationRepository by lazy { kosmos.fakeConfigurationRepository } val configurationInteractor by lazy { kosmos.configurationInteractor } val bouncerRepository by lazy { kosmos.bouncerRepository } - val communalRepository by lazy { kosmos.fakeCommunalRepository } + val communalRepository by lazy { kosmos.fakeCommunalSceneRepository } + val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel } val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor } val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt new file mode 100644 index 000000000000..c4365c9093d2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.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.mediaprojection.data.repository + +import android.app.ActivityManager +import com.android.systemui.mediaprojection.data.model.MediaProjectionState +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeMediaProjectionRepository : MediaProjectionRepository { + override suspend fun switchProjectedTask(task: ActivityManager.RunningTaskInfo) {} + + override val mediaProjectionState: MutableStateFlow<MediaProjectionState> = + MutableStateFlow(MediaProjectionState.NotProjecting) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.kt new file mode 100644 index 000000000000..f253e949375e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepositoryKosmos.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.mediaprojection.data.repository + +import android.os.Handler +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository +import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager + +val Kosmos.fakeMediaProjectionRepository: FakeMediaProjectionRepository by + Kosmos.Fixture { FakeMediaProjectionRepository() } + +val Kosmos.realMediaProjectionRepository by + Kosmos.Fixture { + MediaProjectionManagerRepository( + mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager, + handler = Handler.getMain(), + applicationScope = applicationCoroutineScope, + tasksRepository = activityTaskManagerTasksRepository, + backgroundDispatcher = testDispatcher, + mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt index d344b75c9eb7..5acadd7f192a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt @@ -16,12 +16,11 @@ package com.android.systemui.mediaprojection.taskswitcher -import android.os.Handler import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.mediaprojection.data.repository.realMediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository -import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -40,21 +39,9 @@ val Kosmos.activityTaskManagerTasksRepository by ) } -val Kosmos.mediaProjectionManagerRepository by - Kosmos.Fixture { - MediaProjectionManagerRepository( - mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager, - handler = Handler.getMain(), - applicationScope = applicationCoroutineScope, - tasksRepository = activityTaskManagerTasksRepository, - backgroundDispatcher = testDispatcher, - mediaProjectionServiceHelper = fakeMediaProjectionManager.helper, - ) - } - val Kosmos.taskSwitcherInteractor by Kosmos.Fixture { - TaskSwitchInteractor(mediaProjectionManagerRepository, activityTaskManagerTasksRepository) + TaskSwitchInteractor(realMediaProjectionRepository, activityTaskManagerTasksRepository) } val Kosmos.taskSwitcherViewModel by diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt new file mode 100644 index 000000000000..39ae5eb44c65 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.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.qs.panels.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.settings.userFileManager +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.qsPreferencesRepository by + Kosmos.Fixture { QSPreferencesRepository(userFileManager, userRepository, testDispatcher) } 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..954084b874a0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.core.FakeLogBuffer + +val Kosmos.iconLabelVisibilityInteractor by + Kosmos.Fixture { + IconLabelVisibilityInteractor( + qsPreferencesInteractor, + 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/domain/interactor/QSPreferencesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractorKosmos.kt new file mode 100644 index 000000000000..eb83e325d79b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractorKosmos.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.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.data.repository.qsPreferencesRepository + +val Kosmos.qsPreferencesInteractor by + Kosmos.Fixture { QSPreferencesInteractor(qsPreferencesRepository) } 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/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt index 604c16fd9e74..5ff44e5d33c5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt @@ -17,6 +17,8 @@ package com.android.systemui.qs.pipeline.data.repository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.retail.data.repository.FakeRetailModeRepository +import com.android.systemui.retail.data.repository.RetailModeRepository /** This fake uses 0 as the minimum number of tiles. That means that no tiles is a valid state. */ var Kosmos.fakeMinimumTilesRepository by Kosmos.Fixture { MinimumTilesFixedRepository(0) } @@ -46,3 +48,6 @@ var Kosmos.installedTilesRepository: InstalledTilesComponentRepository by val Kosmos.fakeCustomTileAddedRepository by Kosmos.Fixture { FakeCustomTileAddedRepository() } var Kosmos.customTileAddedRepository: CustomTileAddedRepository by Kosmos.Fixture { fakeCustomTileAddedRepository } + +val Kosmos.fakeRetailModeRepository by Kosmos.Fixture { FakeRetailModeRepository() } +var Kosmos.retailModeRepository: RetailModeRepository by Kosmos.Fixture { fakeRetailModeRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt index b870039982f1..d97a5b2bede2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt @@ -24,6 +24,7 @@ import com.android.systemui.qs.external.tileLifecycleManagerFactory import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository +import com.android.systemui.qs.pipeline.data.repository.retailModeRepository import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.shared.logging.qsLogger import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository @@ -39,6 +40,7 @@ val Kosmos.currentTilesInteractor: CurrentTilesInteractor by installedTilesRepository, userRepository, minimumTilesRepository, + retailModeRepository, customTileStatePersister, { newQSTileFactory }, qsTileFactory, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt index 4f5c9b48690d..5b6fd8c3bd62 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt @@ -45,6 +45,7 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) : other ?: return } check("icon").that(actual.icon()).isEqualTo(other.icon()) + check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes) check("label").that(actual.label).isEqualTo(other.label) check("activationState").that(actual.activationState).isEqualTo(other.activationState) check("secondaryLabel").that(actual.secondaryLabel).isEqualTo(other.secondaryLabel) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt index e0f60e9b3a01..b6194e3f511d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt @@ -19,8 +19,8 @@ package com.android.systemui.qs.ui.adapter import android.content.Context import android.view.View import com.android.systemui.settings.brightness.MirrorController -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull @@ -41,7 +41,7 @@ class FakeQSSceneAdapter( override val customizerAnimationDuration = _animationDuration.asStateFlow() private val _view = MutableStateFlow<View?>(null) - override val qsView: Flow<View> = _view.filterNotNull() + override val qsView: StateFlow<View?> = _view.asStateFlow() private val _state = MutableStateFlow<QSSceneAdapter.State?>(null) val state = _state.filterNotNull() @@ -64,6 +64,8 @@ class FakeQSSceneAdapter( } } + override fun applyLatestExpansionAndSquishiness() {} + fun setCustomizing(value: Boolean) { updateCustomizerFlows(if (value) CustomizerState.Showing else CustomizerState.Hidden) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/FakeOngoingActivityChipInteractor.kt index 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..88bde2ed5d8f 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,31 @@ 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.mediaprojection.data.repository.fakeMediaProjectionRepository +import com.android.systemui.screenrecord.data.repository.screenRecordRepository +import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor +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.mediaProjectionChipInteractor: MediaProjectionChipInteractor by + Kosmos.Fixture { + MediaProjectionChipInteractor( + scope = applicationCoroutineScope, + mediaProjectionRepository = fakeMediaProjectionRepository, + systemClock = fakeSystemClock, + ) + } val Kosmos.callChipInteractor: FakeCallChipInteractor by Kosmos.Fixture { FakeCallChipInteractor() } @@ -29,6 +50,7 @@ val Kosmos.ongoingActivityChipsViewModel: OngoingActivityChipsViewModel by OngoingActivityChipsViewModel( testScope.backgroundScope, screenRecordChipInteractor = screenRecordChipInteractor, + mediaProjectionChipInteractor = mediaProjectionChipInteractor, callChipInteractor = callChipInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt new file mode 100644 index 000000000000..569429f180df --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.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.statusbar.notification.stack + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.notificationStackScrollLayoutController by + Kosmos.Fixture { mock<NotificationStackScrollLayoutController>() } 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/data/repository/TestAudioDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt new file mode 100644 index 000000000000..3ac712918100 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.data.repository + +import android.annotation.SuppressLint +import android.media.AudioDeviceInfo +import android.media.AudioDevicePort + +@SuppressLint("VisibleForTests") +object TestAudioDevicesFactory { + + fun builtInDevice(deviceName: String = "built_in"): AudioDeviceInfo { + return AudioDeviceInfo( + AudioDevicePort.createForTesting( + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + deviceName, + "", + ) + ) + } + + fun wiredDevice(deviceName: String = "wired"): AudioDeviceInfo { + return AudioDeviceInfo( + AudioDevicePort.createForTesting( + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, + deviceName, + "", + ) + ) + } + + fun bluetoothDevice( + deviceName: String = "bt", + deviceAddress: String = "test_address", + ): AudioDeviceInfo { + return AudioDeviceInfo( + AudioDevicePort.createForTesting( + AudioDeviceInfo.TYPE_BLE_HEADSET, + deviceName, + deviceAddress, + ) + ) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt index 63c3ee55ef40..3f51a790aade 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt @@ -22,7 +22,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.volume.data.repository.audioRepository import com.android.systemui.volume.data.repository.audioSharingRepository -import com.android.systemui.volume.localMediaRepositoryFactory import com.android.systemui.volume.mediaOutputInteractor val Kosmos.audioOutputInteractor by @@ -36,7 +35,6 @@ val Kosmos.audioOutputInteractor by bluetoothAdapter, deviceIconInteractor, mediaOutputInteractor, - localMediaRepositoryFactory, audioSharingRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/captioning/CaptioningModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/captioning/CaptioningModuleKosmos.kt new file mode 100644 index 000000000000..e7162d27a031 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/captioning/CaptioningModuleKosmos.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.captioning + +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.view.accessibility.data.repository.captioningInteractor +import com.android.systemui.volume.panel.component.button.ui.composable.ToggleButtonComponent +import com.android.systemui.volume.panel.component.captioning.domain.CaptioningAvailabilityCriteria +import com.android.systemui.volume.panel.component.captioning.ui.viewmodel.captioningViewModel + +val Kosmos.captioningComponent by + Kosmos.Fixture { + ToggleButtonComponent( + captioningViewModel.buttonViewModel, + captioningViewModel::setIsSystemAudioCaptioningEnabled, + ) + } +val Kosmos.captioningAvailabilityCriteria by + Kosmos.Fixture { + CaptioningAvailabilityCriteria( + captioningInteractor, + testScope.backgroundScope, + uiEventLogger, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelKosmos.kt new file mode 100644 index 000000000000..0edd9e026912 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelKosmos.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.volume.panel.component.captioning.ui.viewmodel + +import android.content.applicationContext +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.view.accessibility.data.repository.captioningInteractor + +val Kosmos.captioningViewModel by + Kosmos.Fixture { + CaptioningViewModel( + applicationContext, + captioningInteractor, + testScope.backgroundScope, + uiEventLogger, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt index 9c902cf57fde..680535dfa909 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.data.repository import com.android.settingslib.volume.data.repository.LocalMediaRepository +import kotlinx.coroutines.CoroutineScope class FakeLocalMediaRepositoryFactory(private val defaultProvider: () -> LocalMediaRepository) : LocalMediaRepositoryFactory { @@ -27,6 +28,8 @@ class FakeLocalMediaRepositoryFactory(private val defaultProvider: () -> LocalMe repositories[packageName] = localMediaRepository } - override fun create(packageName: String?): LocalMediaRepository = - repositories[packageName] ?: defaultProvider() + override fun create( + packageName: String?, + coroutineScope: CoroutineScope + ): LocalMediaRepository = repositories[packageName] ?: defaultProvider() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt index 40296099bfe0..141f2426f365 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice +import android.graphics.drawable.Drawable import android.graphics.drawable.TestStubDrawable import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.media.BluetoothMediaDevice @@ -29,29 +30,44 @@ import com.android.systemui.util.mockito.whenever @SuppressLint("StaticFieldLeak") // These are mocks object TestMediaDevicesFactory { - fun builtInMediaDevice(): MediaDevice = mock { - whenever(name).thenReturn("built_in_media") - whenever(icon).thenReturn(TestStubDrawable()) + fun builtInMediaDevice( + deviceName: String = "built_in_media", + deviceIcon: Drawable? = TestStubDrawable(), + ): MediaDevice = mock { + whenever(name).thenReturn(deviceName) + whenever(icon).thenReturn(deviceIcon) } - fun wiredMediaDevice(): MediaDevice = + fun wiredMediaDevice( + deviceName: String = "wired_media", + deviceIcon: Drawable? = TestStubDrawable(), + ): MediaDevice = mock<PhoneMediaDevice> { whenever(deviceType) .thenReturn(MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE) - whenever(name).thenReturn("wired_media") - whenever(icon).thenReturn(TestStubDrawable()) + whenever(name).thenReturn(deviceName) + whenever(icon).thenReturn(deviceIcon) } - fun bluetoothMediaDevice(): MediaDevice { - val bluetoothDevice = mock<BluetoothDevice>() + fun bluetoothMediaDevice( + deviceName: String = "bt_media", + deviceIcon: Drawable? = TestStubDrawable(), + deviceAddress: String = "bt_media_device", + ): BluetoothMediaDevice { + val bluetoothDevice = + mock<BluetoothDevice> { + whenever(name).thenReturn(deviceName) + whenever(address).thenReturn(deviceAddress) + } val cachedBluetoothDevice: CachedBluetoothDevice = mock { whenever(isHearingAidDevice).thenReturn(true) - whenever(address).thenReturn("bt_media_device") + whenever(address).thenReturn(deviceAddress) whenever(device).thenReturn(bluetoothDevice) + whenever(name).thenReturn(deviceName) } return mock<BluetoothMediaDevice> { - whenever(name).thenReturn("bt_media") - whenever(icon).thenReturn(TestStubDrawable()) + whenever(name).thenReturn(deviceName) + whenever(icon).thenReturn(deviceIcon) whenever(cachedDevice).thenReturn(cachedBluetoothDevice) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioModuleKosmos.kt new file mode 100644 index 000000000000..ea5d70d35030 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioModuleKosmos.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.volume.panel.component.spatial + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent +import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria +import com.android.systemui.volume.panel.component.spatial.domain.interactor.spatialAudioComponentInteractor +import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.spatialAudioViewModel + +val Kosmos.spatialAudioComponent by + Kosmos.Fixture { ButtonComponent(spatialAudioViewModel.spatialAudioButton) { _, _ -> } } +val Kosmos.spatialAudioAvailabilityCriteria by + Kosmos.Fixture { SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt new file mode 100644 index 000000000000..95a7b9bb185b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.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.volume.panel.component.spatial.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.media.spatializerInteractor +import com.android.systemui.volume.domain.interactor.audioOutputInteractor + +val Kosmos.spatialAudioComponentInteractor by + Kosmos.Fixture { + SpatialAudioComponentInteractor( + audioOutputInteractor, + spatializerInteractor, + testScope.backgroundScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModelKosmos.kt new file mode 100644 index 000000000000..1b8a3fcfd311 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModelKosmos.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.spatial.ui.viewmodel + +import android.content.applicationContext +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.volume.panel.component.spatial.domain.interactor.spatialAudioComponentInteractor +import com.android.systemui.volume.panel.component.spatial.spatialAudioAvailabilityCriteria + +val Kosmos.spatialAudioViewModel by + Kosmos.Fixture { + SpatialAudioViewModel( + applicationContext, + testScope.backgroundScope, + spatialAudioAvailabilityCriteria, + spatialAudioComponentInteractor, + uiEventLogger, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt index 24d2c2f5dae1..2ba1211a9bdb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.systemui.keyguard.ui.binder +package com.android.systemui.volume.panel.data.repository -import android.os.fakeExecutorHandler +import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos -val Kosmos.keyguardBlueprintViewBinder by - Kosmos.Fixture { KeyguardBlueprintViewBinder(fakeExecutorHandler) } +val Kosmos.volumePanelGlobalStateRepository by + Kosmos.Fixture { VolumePanelGlobalStateRepository(dumpManager) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt index 8862942aa083..a18f498e5441 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt @@ -19,8 +19,10 @@ package com.android.systemui.volume.panel.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.volume.panel.component.bottombar.ui.bottomBarAvailabilityCriteria +import com.android.systemui.volume.panel.component.captioning.captioningAvailabilityCriteria import com.android.systemui.volume.panel.component.mediaoutput.mediaOutputAvailabilityCriteria import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents +import com.android.systemui.volume.panel.component.spatial.spatialAudioAvailabilityCriteria import com.android.systemui.volume.panel.component.volume.volumeSlidersAvailabilityCriteria import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria import com.android.systemui.volume.panel.domain.defaultCriteria @@ -36,6 +38,8 @@ var Kosmos.prodCriteriaByKey: mapOf( VolumePanelComponents.MEDIA_OUTPUT to Provider { mediaOutputAvailabilityCriteria }, VolumePanelComponents.VOLUME_SLIDERS to Provider { volumeSlidersAvailabilityCriteria }, + VolumePanelComponents.CAPTIONING to Provider { captioningAvailabilityCriteria }, + VolumePanelComponents.SPATIAL_AUDIO to Provider { spatialAudioAvailabilityCriteria }, VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarAvailabilityCriteria }, ) } 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/composable/ComponentsFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt index bacf22c0fef6..6bea416fa6a0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt @@ -18,8 +18,10 @@ package com.android.systemui.volume.panel.ui.composable import com.android.systemui.kosmos.Kosmos import com.android.systemui.volume.panel.component.bottombar.ui.bottomBarComponent +import com.android.systemui.volume.panel.component.captioning.captioningComponent import com.android.systemui.volume.panel.component.mediaoutput.mediaOutputComponent import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents +import com.android.systemui.volume.panel.component.spatial.spatialAudioComponent import com.android.systemui.volume.panel.component.volume.volumeSlidersComponent import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent @@ -30,9 +32,11 @@ var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiCo var Kosmos.prodComponentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by Kosmos.Fixture { mapOf( - VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarComponent }, VolumePanelComponents.MEDIA_OUTPUT to Provider { mediaOutputComponent }, VolumePanelComponents.VOLUME_SLIDERS to Provider { volumeSlidersComponent }, + VolumePanelComponents.CAPTIONING to Provider { captioningComponent }, + VolumePanelComponents.SPATIAL_AUDIO to Provider { spatialAudioComponent }, + VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarComponent }, ) } var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by 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/packages/services/VirtualCamera/OWNERS b/packages/services/VirtualCamera/OWNERS deleted file mode 100644 index c66443fb8a14..000000000000 --- a/packages/services/VirtualCamera/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -include /services/companion/java/com/android/server/companion/virtual/OWNERS -caen@google.com -jsebechlebsky@google.com
\ No newline at end of file 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/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodBadIntegrityException.java index 608f301da85d..61d54cba8b54 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt +++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodBadIntegrityException.java @@ -13,18 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.ravenwood.common; -package com.android.systemui.brightness.data.model +public class RavenwoodBadIntegrityException extends RavenwoodRuntimeException { + public RavenwoodBadIntegrityException(String message) { + super(message); + } -@JvmInline -value class LinearBrightness(val floatValue: Float) { - fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness { - return if (floatValue < min.floatValue) { - min - } else if (floatValue > max.floatValue) { - max - } else { - this - } + 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/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 1c57dd3f5d5a..93531508b3eb 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -220,3 +220,13 @@ flag { description: "Feature allows users to change color correction saturation for daltonizer." bug: "322829049" } + +flag { + name: "skip_package_change_before_user_switch" + namespace: "accessibility" + description: "Skip onSomePackageChanged callback if the SwitchUser signal is not received yet." + bug: "340927041" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 053a1a7c4ae9..20b727cd6f09 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -588,6 +588,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return Thread.holdsLock(mLock); } + /** + * Returns if the service is initialized. + * + * The service is considered initialized when the user switch happened. + */ + private boolean isServiceInitializedLocked() { + return mInitialized; + } + @Override public int getCurrentUserIdLocked() { return mCurrentUserId; @@ -6234,6 +6243,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (userId != mManagerService.getCurrentUserIdLocked()) { return; } + + // Only continue setting up the packages if the service has been initialized. + // See: b/340927041 + if (Flags.skipPackageChangeBeforeUserSwitch() + && !mManagerService.isServiceInitializedLocked()) { + Slog.w(LOG_TAG, + "onSomePackagesChanged: service not initialized, skip the callback."); + return; + } mManagerService.onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); } 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/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index dc1cfb92c3b8..ddccb3731cc1 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -4008,20 +4008,10 @@ public class UserBackupManagerService { } private PackageInfo getPackageInfoForBMMLogging(String packageName) { - try { - return mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId); - } catch (NameNotFoundException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Asked to get PackageInfo for BMM logging of nonexistent pkg " - + packageName)); - - PackageInfo packageInfo = new PackageInfo(); - packageInfo.packageName = packageName; + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; - return packageInfo; - } + return packageInfo; } /** Hand off a restore session. */ 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/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 30e4a3eb77e6..0d0c21dcf49c 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -34,7 +34,6 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.utils.PackageUtils.getPackageInfo; import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; -import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; @@ -335,12 +334,6 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, "get associations"); - if (!checkCallerCanManageCompanionDevice(getContext())) { - // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to - // request the feature (also: the caller is the app itself). - enforceUsesCompanionDeviceFeature(getContext(), userId, packageName); - } - return mAssociationStore.getActiveAssociationsByPackage(userId, packageName); } diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java index d09d7e672f9d..3fbd8560b82c 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java @@ -347,6 +347,8 @@ public class AssociationRequestsProcessor { * Set association tag. */ public void setAssociationTag(int associationId, String tag) { + Slog.i(TAG, "Setting association tag=[" + tag + "] to id=[" + associationId + "]..."); + AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( associationId); association = (new AssociationInfo.Builder(association)).setTag(tag).build(); diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java index 29e8095f8680..757abd927ac8 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java @@ -18,7 +18,7 @@ package com.android.server.companion.association; import static com.android.server.companion.utils.MetricUtils.logCreateAssociation; import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation; -import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage; +import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; import android.annotation.IntDef; import android.annotation.NonNull; @@ -457,6 +457,10 @@ public class AssociationStore { /** * Get association by id with caller checks. + * + * If the association is not found, an IllegalArgumentException would be thrown. + * + * If the caller can't access the association, a SecurityException would be thrown. */ @NonNull public AssociationInfo getAssociationWithCallerChecks(int associationId) { @@ -466,13 +470,9 @@ public class AssociationStore { "getAssociationWithCallerChecks() Association id=[" + associationId + "] doesn't exist."); } - if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(), - association.getPackageName())) { - return association; - } - - throw new IllegalArgumentException( - "The caller can't interact with the association id=[" + associationId + "]."); + enforceCallerCanManageAssociationsForPackage(mContext, association.getUserId(), + association.getPackageName(), null); + return association; } /** diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java index 8c1116b7a612..6f0baef019b3 100644 --- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java +++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java @@ -98,7 +98,6 @@ public class DisassociationProcessor { Slog.i(TAG, "Disassociating id=[" + id + "]..."); final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id); - final int userId = association.getUserId(); final String packageName = association.getPackageName(); final String deviceProfile = association.getDeviceProfile(); diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 026d29c9f821..00e049c1e1ff 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -122,7 +122,6 @@ public class SystemDataTransferProcessor { */ public boolean isPermissionTransferUserConsented(int associationId) { mAssociationStore.getAssociationWithCallerChecks(associationId); - PermissionSyncRequest request = getPermissionSyncRequest(associationId); if (request == null) { return false; @@ -147,12 +146,12 @@ public class SystemDataTransferProcessor { return null; } - final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( - associationId); - Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId + "] associationId [" + associationId + "]"); + final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( + associationId); + // Create an internal intent to launch the user consent dialog final Bundle extras = new Bundle(); PermissionSyncRequest request = new PermissionSyncRequest(associationId); @@ -220,7 +219,9 @@ public class SystemDataTransferProcessor { * Enable perm sync for the association */ public void enablePermissionsSync(int associationId) { - int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId(); + AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( + associationId); + int userId = association.getUserId(); PermissionSyncRequest request = new PermissionSyncRequest(associationId); request.setUserConsented(true); mSystemDataTransferRequestStore.writeRequest(userId, request); @@ -230,7 +231,9 @@ public class SystemDataTransferProcessor { * Disable perm sync for the association */ public void disablePermissionsSync(int associationId) { - int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId(); + AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( + associationId); + int userId = association.getUserId(); PermissionSyncRequest request = new PermissionSyncRequest(associationId); request.setUserConsented(false); mSystemDataTransferRequestStore.writeRequest(userId, request); @@ -241,8 +244,9 @@ public class SystemDataTransferProcessor { */ @Nullable public PermissionSyncRequest getPermissionSyncRequest(int associationId) { - int userId = mAssociationStore.getAssociationWithCallerChecks(associationId) - .getUserId(); + AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( + associationId); + int userId = association.getUserId(); List<SystemDataTransferRequest> requests = mSystemDataTransferRequestStore.readRequestsByAssociationId(userId, associationId); @@ -259,7 +263,9 @@ public class SystemDataTransferProcessor { */ public void removePermissionSyncRequest(int associationId) { Binder.withCleanCallingIdentity(() -> { - int userId = mAssociationStore.getAssociationById(associationId).getUserId(); + AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( + associationId); + int userId = association.getUserId(); mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId); }); } diff --git a/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java index 6cdc02ec67a2..0a1bc0fdf4b8 100644 --- a/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java +++ b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java @@ -241,7 +241,8 @@ class BleDeviceProcessor implements AssociationStore.OnChangeListener { final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - checkBleState(); + // Post to the main thread to make sure it is a Non-Blocking call. + new Handler(Looper.getMainLooper()).post(() -> checkBleState()); } }; diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java index f397814f7ad7..796d2851760f 100644 --- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java @@ -149,21 +149,6 @@ public final class PermissionsUtils { } /** - * Check if the caller is system UID or the provided user. - */ - public static boolean checkCallerIsSystemOr(@UserIdInt int userId, - @NonNull String packageName) { - final int callingUid = getCallingUid(); - if (callingUid == SYSTEM_UID) return true; - - if (getCallingUserId() != userId) return false; - - if (!checkPackage(callingUid, packageName)) return false; - - return true; - } - - /** * Check if the calling user id matches the userId, and if the package belongs to * the calling uid. */ @@ -184,21 +169,30 @@ public final class PermissionsUtils { } /** - * Check if the caller holds the necessary permission to manage companion devices. - */ - public static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) { - if (getCallingUid() == SYSTEM_UID) return true; - - return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED; - } - - /** * Require the caller to be able to manage the associations for the package. */ public static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName, @Nullable String actionDescription) { - if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return; + final int callingUid = getCallingUid(); + + // If the caller is the system + if (callingUid == SYSTEM_UID) { + return; + } + + // If caller can manage the package or has the permissions to manage companion devices + boolean canInteractAcrossUsers = context.checkCallingPermission(INTERACT_ACROSS_USERS) + == PERMISSION_GRANTED; + boolean canManageCompanionDevices = context.checkCallingPermission(MANAGE_COMPANION_DEVICES) + == PERMISSION_GRANTED; + if (getCallingUserId() == userId) { + if (checkPackage(callingUid, packageName) || canManageCompanionDevices) { + return; + } + } else if (canInteractAcrossUsers && canManageCompanionDevices) { + return; + } throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have " + "permissions to " @@ -219,25 +213,6 @@ public final class PermissionsUtils { } } - /** - * Check if the caller is either: - * <ul> - * <li> the package itself - * <li> the System ({@link android.os.Process#SYSTEM_UID}) - * <li> holds {@link Manifest.permission#MANAGE_COMPANION_DEVICES} and, if belongs to a - * different user, also holds {@link Manifest.permission#INTERACT_ACROSS_USERS}. - * </ul> - * @return whether the caller is one of the above. - */ - public static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context, - @UserIdInt int userId, @NonNull String packageName) { - if (checkCallerIsSystemOr(userId, packageName)) return true; - - if (!checkCallerCanInteractWithUserId(context, userId)) return false; - - return checkCallerCanManageCompanionDevice(context); - } - private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) { try { return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED; diff --git a/services/core/Android.bp b/services/core/Android.bp index 0fdf6d0fd507..f1339e91d3d4 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -232,7 +232,6 @@ java_library_static { "android.hardware.rebootescrow-V1-java", "android.hardware.power.stats-V2-java", "android.hidl.manager-V1.2-java", - "audio-permission-aidl-java", "cbor-java", "com.android.media.audio-aconfig-java", "icu4j_calendar_astronomer", diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index a61925732256..966478e33c73 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -138,6 +138,12 @@ public class PackageWatchdog { static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10); + // Time needed to apply mitigation + private static final String MITIGATION_WINDOW_MS = + "persist.device_config.configuration.mitigation_window_ms"; + @VisibleForTesting + static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5); + // Threshold level at which or above user might experience significant disruption. private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD = "persist.device_config.configuration.major_user_impact_level_threshold"; @@ -210,6 +216,9 @@ public class PackageWatchdog { @GuardedBy("mLock") private boolean mSyncRequired = false; + @GuardedBy("mLock") + private long mLastMitigation = -1000000; + @FunctionalInterface @VisibleForTesting interface SystemClock { @@ -400,6 +409,16 @@ public class PackageWatchdog { Slog.w(TAG, "Could not resolve a list of failing packages"); return; } + synchronized (mLock) { + final long now = mSystemClock.uptimeMillis(); + if (Flags.recoverabilityDetection()) { + if (now >= mLastMitigation + && (now - mLastMitigation) < getMitigationWindowMs()) { + Slog.i(TAG, "Skipping onPackageFailure mitigation"); + return; + } + } + } mLongTaskHandler.post(() -> { synchronized (mLock) { if (mAllObservers.isEmpty()) { @@ -500,10 +519,17 @@ public class PackageWatchdog { int currentObserverImpact, int mitigationCount) { if (currentObserverImpact < getUserImpactLevelLimit()) { + synchronized (mLock) { + mLastMitigation = mSystemClock.uptimeMillis(); + } currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount); } } + private long getMitigationWindowMs() { + return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS); + } + /** * Called when the system server boots. If the system server is detected to be in a boot loop, diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 4dd3a8f67b0d..b35959f1a6e8 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3980,7 +3980,7 @@ class StorageManagerService extends IStorageManager.Stub if (resUuids.contains(rec.fsUuid)) continue; // Treat as recent if mounted within the last week - if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) { + if (rec.lastSeenMillis > 0 && rec.lastSeenMillis >= lastWeek) { final StorageVolume userVol = rec.buildStorageVolume(mContext); res.add(userVol); resUuids.add(userVol.getUuid()); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index eccc41daa9bb..f7278e9f986d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -40,6 +40,8 @@ import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED; import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT; import static android.app.ActivityManager.RESTRICTION_REASON_USAGE; +import static android.app.ActivityManager.RESTRICTION_SOURCE_SYSTEM; +import static android.app.ActivityManager.RESTRICTION_SOURCE_USER; import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN; @@ -5148,7 +5150,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (android.app.Flags.appRestrictionsApi() && wasForceStopped) { noteAppRestrictionEnabled(app.info.packageName, app.uid, RESTRICTION_LEVEL_FORCE_STOPPED, false, - RESTRICTION_REASON_USAGE, "unknown", 0L); + RESTRICTION_REASON_USAGE, "unknown", RESTRICTION_SOURCE_USER, 0L); } if (!sendBroadcast) { @@ -6023,46 +6025,47 @@ 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; + } + } } } @@ -10304,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); @@ -14399,7 +14403,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (wasStopped) { noteAppRestrictionEnabled(app.packageName, app.uid, RESTRICTION_LEVEL_FORCE_STOPPED, false, - RESTRICTION_REASON_DEFAULT, "restore", 0L); + RESTRICTION_REASON_DEFAULT, "restore", + RESTRICTION_SOURCE_SYSTEM, 0L); } } catch (NameNotFoundException e) { Slog.w(TAG, "No such package", e); @@ -20316,12 +20321,14 @@ public class ActivityManagerService extends IActivityManager.Stub * Log the reason for changing an app restriction. Purely used for logging purposes and does not * cause any change to app state. * - * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, String, long) + * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, + * String, int, long) */ @Override public void noteAppRestrictionEnabled(String packageName, int uid, @RestrictionLevel int restrictionType, boolean enabled, - @ActivityManager.RestrictionReason int reason, String subReason, long threshold) { + @ActivityManager.RestrictionReason int reason, String subReason, + @ActivityManager.RestrictionSource int source, long threshold) { if (!android.app.Flags.appRestrictionsApi()) return; enforceCallingPermission(android.Manifest.permission.DEVICE_POWER, @@ -20334,7 +20341,7 @@ public class ActivityManagerService extends IActivityManager.Stub uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); } mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType, - enabled, reason, subReason, threshold); + enabled, reason, subReason, source, threshold); } finally { Binder.restoreCallingIdentity(callingId); } diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index f5f1928c7c72..4a31fd1f46e4 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -31,11 +31,10 @@ import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY; import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT; import static android.app.ActivityManager.RESTRICTION_REASON_DORMANT; -import static android.app.ActivityManager.RESTRICTION_REASON_REMOTE_TRIGGER; +import static android.app.ActivityManager.RESTRICTION_REASON_POLICY; import static android.app.ActivityManager.RESTRICTION_REASON_SYSTEM_HEALTH; import static android.app.ActivityManager.RESTRICTION_REASON_USAGE; import static android.app.ActivityManager.RESTRICTION_REASON_USER; -import static android.app.ActivityManager.RESTRICTION_REASON_USER_NUDGED; import static android.app.ActivityManager.RESTRICTION_SUBREASON_MAX_LENGTH; import static android.app.ActivityManager.UID_OBSERVER_ACTIVE; import static android.app.ActivityManager.UID_OBSERVER_GONE; @@ -103,6 +102,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManager.RestrictionLevel; import android.app.ActivityManager.RestrictionReason; +import android.app.ActivityManager.RestrictionSource; import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener; import android.app.AppOpsManager; @@ -2378,7 +2378,8 @@ public final class AppRestrictionController { */ public void noteAppRestrictionEnabled(String packageName, int uid, @RestrictionLevel int restrictionType, boolean enabled, - @RestrictionReason int reason, String subReason, long threshold) { + @RestrictionReason int reason, String subReason, @RestrictionSource int source, + long threshold) { if (DEBUG_BG_RESTRICTION_CONTROLLER) { Slog.i(TAG, (enabled ? "restricted " : "unrestricted ") + packageName + " to " + restrictionType + " reason=" + reason + ", subReason=" + subReason @@ -2397,7 +2398,8 @@ public final class AppRestrictionController { enabled, getRestrictionChangeReasonStatsd(reason, subReason), subReason, - threshold); + threshold, + source); } private int getRestrictionTypeStatsd(@RestrictionLevel int level) { @@ -2433,12 +2435,10 @@ public final class AppRestrictionController { FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USAGE; case RESTRICTION_REASON_USER -> FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER; - case RESTRICTION_REASON_USER_NUDGED -> - FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER_NUDGED; case RESTRICTION_REASON_SYSTEM_HEALTH -> FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_SYSTEM_HEALTH; - case RESTRICTION_REASON_REMOTE_TRIGGER -> - FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_REMOTE_TRIGGER; + case RESTRICTION_REASON_POLICY -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_POLICY; default -> FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_OTHER; }; diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 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/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 8647750d510f..ab34dd4477fd 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2205,12 +2205,15 @@ public class OomAdjuster { != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; if (roForegroundAudioControl()) { // flag check - final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK - | FOREGROUND_SERVICE_TYPE_CAMERA - | FOREGROUND_SERVICE_TYPE_MICROPHONE - | FOREGROUND_SERVICE_TYPE_PHONE_CALL; - capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0 - ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0; + // TODO revisit restriction of FOREGROUND_AUDIO_CONTROL when it can be + // limited to specific FGS types + //final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + // | FOREGROUND_SERVICE_TYPE_CAMERA + // | FOREGROUND_SERVICE_TYPE_MICROPHONE + // | FOREGROUND_SERVICE_TYPE_PHONE_CALL; + //capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0 + // ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0; + capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; } final boolean enabled = state.getCachedCompatChange( 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/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index e066c23d4d59..fb6895b8f234 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -571,6 +571,7 @@ public final class AppHibernationService extends SystemService { mIActivityManager.noteAppRestrictionEnabled( packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true, ActivityManager.RESTRICTION_REASON_DORMANT, null, + ActivityManager.RESTRICTION_SOURCE_SYSTEM, /* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L); } // No need to log the unhibernate case as an unstop is logged already in ActivityMS diff --git a/services/core/java/com/android/server/appop/AppOpsRecentAccessPersistence.java b/services/core/java/com/android/server/appop/AppOpsRecentAccessPersistence.java new file mode 100644 index 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/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 1dc1846fbb96..1d21ccb62b8c 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -15,6 +15,8 @@ */ package com.android.server.audio; +import static android.media.audio.Flags.scoManagedByAudio; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.compat.CompatChanges; @@ -54,6 +56,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.sysprop.BluetoothProperties; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -74,7 +77,6 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; - /** * @hide * (non final for mocking/spying) @@ -167,6 +169,15 @@ public class AudioDeviceBroker { @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2) public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L; + /** Indicates if headset profile connection and SCO audio control use the new implementation + * aligned with other BT profiles. True if both the feature flag Flags.scoManagedByAudio() and + * the system property audio.sco.managed.by.audio are true. + */ + private final boolean mScoManagedByAudio; + /*package*/ boolean isScoManagedByAudio() { + return mScoManagedByAudio; + } + //------------------------------------------------------------------- /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service, @NonNull AudioSystemAdapter audioSystem) { @@ -176,7 +187,8 @@ public class AudioDeviceBroker { mDeviceInventory = new AudioDeviceInventory(this); mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext); mAudioSystem = audioSystem; - + mScoManagedByAudio = scoManagedByAudio() + && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false); init(); } @@ -192,7 +204,8 @@ public class AudioDeviceBroker { mDeviceInventory = mockDeviceInventory; mSystemServer = mockSystemServer; mAudioSystem = audioSystem; - + mScoManagedByAudio = scoManagedByAudio() + && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false); init(); } @@ -400,24 +413,24 @@ public class AudioDeviceBroker { if (client == null) { return; } - - boolean isBtScoRequested = isBluetoothScoRequested(); - if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { - if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { - Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: " - + uid); - // clean up or restore previous client selection - if (prevClientDevice != null) { - addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged); - } else { - removeCommunicationRouteClient(cb, true); + if (!mScoManagedByAudio) { + boolean isBtScoRequested = isBluetoothScoRequested(); + if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { + if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { + Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: " + + uid); + // clean up or restore previous client selection + if (prevClientDevice != null) { + addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged); + } else { + removeCommunicationRouteClient(cb, true); + } + postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); } - postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } else if (!isBtScoRequested && wasBtScoRequested) { + mBtHelper.stopBluetoothSco(eventSource); } - } else if (!isBtScoRequested && wasBtScoRequested) { - mBtHelper.stopBluetoothSco(eventSource); } - // In BT classic for communication, the device changes from a2dp to sco device, but for // LE Audio it stays the same and we must trigger the proper stream volume alignment, if // LE Audio communication device is activated after the audio system has already switched to @@ -1685,6 +1698,8 @@ public class AudioDeviceBroker { pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner); + pw.println("\n" + prefix + "mScoManagedByAudio: " + mScoManagedByAudio); + mBtHelper.dump(pw, prefix); } @@ -1837,10 +1852,10 @@ public class AudioDeviceBroker { ? mAudioService.getBluetoothContextualVolumeStream() : AudioSystem.STREAM_DEFAULT); if (btInfo.mProfile == BluetoothProfile.LE_AUDIO - || btInfo.mProfile - == BluetoothProfile.HEARING_AID) { - onUpdateCommunicationRouteClient( - isBluetoothScoRequested(), + || btInfo.mProfile == BluetoothProfile.HEARING_AID + || (mScoManagedByAudio + && btInfo.mProfile == BluetoothProfile.HEADSET)) { + onUpdateCommunicationRouteClient(isBluetoothScoRequested(), "setBluetoothActiveDevice"); } } @@ -2511,7 +2526,7 @@ public class AudioDeviceBroker { setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(), BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource); } else { - if (!isBluetoothScoRequested() && wasBtScoRequested) { + if (!mScoManagedByAudio && !isBluetoothScoRequested() && wasBtScoRequested) { mBtHelper.stopBluetoothSco(eventSource); } updateCommunicationRoute(eventSource); @@ -2815,4 +2830,5 @@ public class AudioDeviceBroker { void clearDeviceInventory() { mDeviceInventory.clearDeviceInventory(); } + } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index c9612caf67a0..287c92f86f0f 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -293,6 +293,7 @@ public class AudioDeviceInventory { Iterator<Entry<Pair<Integer, String>, AdiDeviceState>> iterator = mDeviceInventory.entrySet().iterator(); if (iterator.hasNext()) { + iterator.next(); iterator.remove(); } } @@ -858,6 +859,15 @@ public class AudioDeviceInventory { btInfo, streamType, codec, "onSetBtActiveDevice"); } break; + case BluetoothProfile.HEADSET: + if (mDeviceBroker.isScoManagedByAudio()) { + if (switchToUnavailable) { + mDeviceBroker.onSetBtScoActiveDevice(null); + } else if (switchToAvailable) { + mDeviceBroker.onSetBtScoActiveDevice(btInfo.mDevice); + } + } + break; default: throw new IllegalArgumentException("Invalid profile " + BluetoothProfile.getProfileName(btInfo.mProfile)); } diff --git a/services/core/java/com/android/server/audio/AudioPolicyFacade.java b/services/core/java/com/android/server/audio/AudioPolicyFacade.java index 02e80d611f3f..f652b33b3fd3 100644 --- a/services/core/java/com/android/server/audio/AudioPolicyFacade.java +++ b/services/core/java/com/android/server/audio/AudioPolicyFacade.java @@ -16,12 +16,14 @@ package com.android.server.audio; +import com.android.media.permission.INativePermissionController; /** * Facade to IAudioPolicyService which fulfills AudioService dependencies. * See @link{IAudioPolicyService.aidl} */ public interface AudioPolicyFacade { - public boolean isHotwordStreamSupported(boolean lookbackAudio); + public INativePermissionController getPermissionController(); + public void registerOnStartTask(Runnable r); } diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java new file mode 100644 index 000000000000..5ea3c4bf538d --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java @@ -0,0 +1,149 @@ +/* + * 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.audio; + +import android.annotation.Nullable; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.media.permission.INativePermissionController; +import com.android.media.permission.UidPackageState; +import com.android.server.pm.pkg.PackageState; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +/** Responsible for synchronizing system server permission state to the native audioserver. */ +public class AudioServerPermissionProvider { + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private INativePermissionController mDest; + + @GuardedBy("mLock") + private final Map<Integer, Set<String>> mPackageMap; + + /** + * @param appInfos - PackageState for all apps on the device, used to populate init state + */ + public AudioServerPermissionProvider(Collection<PackageState> appInfos) { + // Initialize the package state + mPackageMap = generatePackageMappings(appInfos); + } + + /** + * Called whenever audioserver starts (or started before us) + * + * @param pc - The permission controller interface from audioserver, which we push updates to + */ + public void onServiceStart(@Nullable INativePermissionController pc) { + if (pc == null) return; + synchronized (mLock) { + mDest = pc; + resetNativePackageState(); + } + } + + /** + * Called when a package is added or removed + * + * @param uid - uid of modified package (only app-id matters) + * @param packageName - the (new) packageName + * @param isRemove - true if the package is being removed, false if it is being added + */ + public void onModifyPackageState(int uid, String packageName, boolean isRemove) { + // No point in maintaining package mappings for uids of different users + uid = UserHandle.getAppId(uid); + synchronized (mLock) { + // Update state + Set<String> packages; + if (!isRemove) { + packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1)); + packages.add(packageName); + } else { + packages = mPackageMap.get(uid); + if (packages != null) { + packages.remove(packageName); + if (packages.isEmpty()) mPackageMap.remove(uid); + } + } + // Push state to destination + if (mDest == null) { + return; + } + var state = new UidPackageState(); + state.uid = uid; + state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList(); + try { + mDest.updatePackagesForUid(state); + } catch (RemoteException e) { + // We will re-init the state when the service comes back up + mDest = null; + } + } + } + + /** Called when full syncing package state to audioserver. */ + @GuardedBy("mLock") + private void resetNativePackageState() { + if (mDest == null) return; + List<UidPackageState> states = + mPackageMap.entrySet().stream() + .map( + entry -> { + UidPackageState state = new UidPackageState(); + state.uid = entry.getKey(); + state.packageNames = List.copyOf(entry.getValue()); + return state; + }) + .toList(); + try { + mDest.populatePackagesForUids(states); + } catch (RemoteException e) { + // We will re-init the state when the service comes back up + mDest = null; + } + } + + /** + * Aggregation operation on all package states list: groups by states by app-id and merges the + * packageName for each state into an ArraySet. + */ + private static Map<Integer, Set<String>> generatePackageMappings( + Collection<PackageState> appInfos) { + Collector<PackageState, Object, Set<String>> reducer = + Collectors.mapping( + (PackageState p) -> p.getPackageName(), + Collectors.toCollection(() -> new ArraySet(1))); + + return appInfos.stream() + .collect( + Collectors.groupingBy( + /* predicate */ (PackageState p) -> p.getAppId(), + /* factory */ HashMap::new, + /* downstream collector */ reducer)); + } +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 7deef2ffb5dd..ef65b2523024 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -16,8 +16,25 @@ package com.android.server.audio; +import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; +import static android.Manifest.permission.BLUETOOTH_STACK; +import static android.Manifest.permission.CALL_AUDIO_INTERCEPTION; +import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; +import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT; +import static android.Manifest.permission.CAPTURE_MEDIA_OUTPUT; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.MODIFY_AUDIO_ROUTING; +import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS; import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; +import static android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS; +import static android.Manifest.permission.MODIFY_PHONE_STATE; +import static android.Manifest.permission.QUERY_AUDIO_STATE; +import static android.Manifest.permission.WRITE_SETTINGS; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.EXTRA_ARCHIVAL; +import static android.content.Intent.EXTRA_REPLACING; import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; @@ -35,6 +52,7 @@ import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency; import static android.media.audio.Flags.focusFreezeTestApi; import static android.media.audio.Flags.roForegroundAudioControl; +import static android.media.audio.Flags.scoManagedByAudio; import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.INVALID_UID; @@ -43,7 +61,9 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static com.android.media.audio.Flags.absVolumeIndexFix; import static com.android.media.audio.Flags.alarmMinVolumeZero; +import static com.android.media.audio.Flags.audioserverPermissions; import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume; import static com.android.media.audio.Flags.ringerModeAffectsAlarm; import static com.android.media.audio.Flags.setStreamVolumeOrder; @@ -225,15 +245,18 @@ import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; +import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent; import com.android.server.audio.AudioServiceEvents.PhoneStateEvent; import com.android.server.audio.AudioServiceEvents.VolChangedBroadcastEvent; import com.android.server.audio.AudioServiceEvents.VolumeEvent; +import com.android.server.pm.PackageManagerLocal; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerInternal.UserRestrictionsListener; import com.android.server.pm.UserManagerService; +import com.android.server.pm.pkg.PackageState; import com.android.server.utils.EventLogger; import com.android.server.wm.ActivityTaskManagerInternal; @@ -258,6 +281,7 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; @@ -288,6 +312,8 @@ public class AudioService extends IAudioService.Stub private final SettingsAdapter mSettings; private final AudioPolicyFacade mAudioPolicy; + private final AudioServerPermissionProvider mPermissionProvider; + private final MusicFxHelper mMusicFxHelper; /** Debug audio mode */ @@ -618,6 +644,17 @@ public class AudioService extends IAudioService.Stub // If absolute volume is supported in AVRCP device private volatile boolean mAvrcpAbsVolSupported = false; + private final Object mCachedAbsVolDrivingStreamsLock = new Object(); + // Contains for all the device types which support absolute volume the current streams that + // are driving the volume changes + @GuardedBy("mCachedAbsVolDrivingStreamsLock") + private final HashMap<Integer, Integer> mCachedAbsVolDrivingStreams = new HashMap<>( + Map.of(AudioSystem.DEVICE_OUT_BLE_HEADSET, AudioSystem.STREAM_MUSIC, + AudioSystem.DEVICE_OUT_BLE_SPEAKER, AudioSystem.STREAM_MUSIC, + AudioSystem.DEVICE_OUT_BLE_BROADCAST, AudioSystem.STREAM_MUSIC, + AudioSystem.DEVICE_OUT_HEARING_AID, AudioSystem.STREAM_MUSIC + )); + /** * Default stream type used for volume control in the absence of playback * e.g. user on homescreen, no app playing anything, presses hardware volume buttons, this @@ -995,14 +1032,22 @@ public class AudioService extends IAudioService.Stub public Lifecycle(Context context) { super(context); + var audioserverLifecycleExecutor = Executors.newSingleThreadExecutor(); + var audioPolicyFacade = new DefaultAudioPolicyFacade(audioserverLifecycleExecutor); mService = new AudioService(context, AudioSystemAdapter.getDefaultAdapter(), SystemServerAdapter.getDefaultAdapter(context), SettingsAdapter.getDefaultAdapter(), new AudioVolumeGroupHelper(), - new DefaultAudioPolicyFacade(), - null); - + audioPolicyFacade, + null, + context.getSystemService(AppOpsManager.class), + PermissionEnforcer.fromContext(context), + audioserverPermissions() ? + initializeAudioServerPermissionProvider( + context, audioPolicyFacade, audioserverLifecycleExecutor) : + null + ); } @Override @@ -1079,25 +1124,6 @@ public class AudioService extends IAudioService.Stub /** * @param context * @param audioSystem Adapter for {@link AudioSystem} - * @param systemServer Adapter for privileged functionality for system server components - * @param settings Adapter for {@link Settings} - * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup} - * @param audioPolicy Interface of a facade to IAudioPolicyManager - * @param looper Looper to use for the service's message handler. If this is null, an - * {@link AudioSystemThread} is created as the messaging thread instead. - */ - public AudioService(Context context, AudioSystemAdapter audioSystem, - SystemServerAdapter systemServer, SettingsAdapter settings, - AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, - @Nullable Looper looper) { - this (context, audioSystem, systemServer, settings, audioVolumeGroupHelper, - audioPolicy, looper, context.getSystemService(AppOpsManager.class), - PermissionEnforcer.fromContext(context)); - } - - /** - * @param context - * @param audioSystem Adapter for {@link AudioSystem} * @param systemServer Adapter for privilieged functionality for system server components * @param settings Adapter for {@link Settings} * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup} @@ -1111,13 +1137,16 @@ public class AudioService extends IAudioService.Stub public AudioService(Context context, AudioSystemAdapter audioSystem, SystemServerAdapter systemServer, SettingsAdapter settings, AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, - @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer) { + @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer, + /* @NonNull */ AudioServerPermissionProvider permissionProvider) { super(enforcer); sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()")); mContext = context; mContentResolver = context.getContentResolver(); mAppOps = appOps; + mPermissionProvider = permissionProvider; + mAudioSystem = audioSystem; mSystemServer = systemServer; mAudioVolumeGroupHelper = audioVolumeGroupHelper; @@ -1458,6 +1487,13 @@ public class AudioService extends IAudioService.Stub // check on volume initialization checkVolumeRangeInitialization("AudioService()"); + + synchronized (mCachedAbsVolDrivingStreamsLock) { + mCachedAbsVolDrivingStreams.forEach((dev, stream) -> { + mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true, + stream); + }); + } } private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener = @@ -1490,7 +1526,9 @@ public class AudioService extends IAudioService.Stub // Register for device connection intent broadcasts. IntentFilter intentFilter = new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); - intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); + if (!mDeviceBroker.isScoManagedByAudio()) { + intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); + } intentFilter.addAction(Intent.ACTION_DOCK_EVENT); if (mDisplayManager == null) { intentFilter.addAction(Intent.ACTION_SCREEN_ON); @@ -1888,7 +1926,6 @@ public class AudioService extends IAudioService.Stub } mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect); - mSoundDoseHelper.reset(); // Restore rotation information. if (mMonitorRotation) { @@ -1896,9 +1933,19 @@ public class AudioService extends IAudioService.Stub } onIndicateSystemReady(); + + synchronized (mCachedAbsVolDrivingStreamsLock) { + mCachedAbsVolDrivingStreams.forEach((dev, stream) -> { + mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true, + stream); + }); + } + // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); + mSoundDoseHelper.reset(/*resetISoundDose=*/true); + sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_SERVER_STATE, SENDMSG_QUEUE, 1, 0, null, 0); @@ -2075,7 +2122,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @see AudioManager#setSupportedSystemUsages(int[]) */ @@ -2090,7 +2137,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @see AudioManager#getSupportedSystemUsages() */ @@ -2110,7 +2157,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @return the {@link android.media.audiopolicy.AudioProductStrategy} discovered from the * platform configuration file. @@ -2123,7 +2170,8 @@ 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, MODIFY_AUDIO_ROUTING }) /** * @return the List of {@link android.media.audiopolicy.AudioVolumeGroup} discovered from the * platform configuration file. @@ -2581,7 +2629,7 @@ public class AudioService extends IAudioService.Stub Log.w(TAG, "audioFormat to enable is not a surround format."); return false; } - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS) + if (mContext.checkCallingOrSelfPermission(WRITE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing WRITE_SETTINGS permission"); } @@ -2605,7 +2653,7 @@ public class AudioService extends IAudioService.Stub return true; } - @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SETTINGS) + @android.annotation.EnforcePermission(WRITE_SETTINGS) /** @see AudioManager#setEncodedSurroundMode(int) */ @Override public boolean setEncodedSurroundMode(@AudioManager.EncodedSurroundOutputMode int mode) { @@ -2783,7 +2831,7 @@ public class AudioService extends IAudioService.Stub if (!TextUtils.isEmpty(packageName)) { PackageManager pm = mContext.getPackageManager(); - if (pm.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName) + if (pm.checkPermission(CAPTURE_AUDIO_HOTWORD, packageName) == PackageManager.PERMISSION_GRANTED) { try { assistantUid = pm.getPackageUidAsUser(packageName, getCurrentUserId()); @@ -2970,7 +3018,7 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, * List<AudioDeviceAttributes>) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public int setPreferredDevicesForStrategy(int strategy, List<AudioDeviceAttributes> devices) { super.setPreferredDevicesForStrategy_enforcePermission(); if (devices == null) { @@ -2998,7 +3046,7 @@ public class AudioService extends IAudioService.Stub return status; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */ public int removePreferredDevicesForStrategy(int strategy) { super.removePreferredDevicesForStrategy_enforcePermission(); @@ -3014,7 +3062,7 @@ public class AudioService extends IAudioService.Stub return status; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy) * @see AudioManager#getPreferredDevicesForStrategy(AudioProductStrategy) @@ -3046,7 +3094,7 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#setDeviceAsNonDefaultForStrategy(AudioProductStrategy, * List<AudioDeviceAttributes>) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public int setDeviceAsNonDefaultForStrategy(int strategy, @NonNull AudioDeviceAttributes device) { super.setDeviceAsNonDefaultForStrategy_enforcePermission(); @@ -3075,7 +3123,7 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#removeDeviceAsNonDefaultForStrategy(AudioProductStrategy, * AudioDeviceAttributes) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public int removeDeviceAsNonDefaultForStrategy(int strategy, AudioDeviceAttributes device) { super.removeDeviceAsNonDefaultForStrategy_enforcePermission(); @@ -3101,7 +3149,7 @@ public class AudioService extends IAudioService.Stub /** * @see AudioManager#getNonDefaultDevicesForStrategy(AudioProductStrategy) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public List<AudioDeviceAttributes> getNonDefaultDevicesForStrategy(int strategy) { super.getNonDefaultDevicesForStrategy_enforcePermission(); List<AudioDeviceAttributes> devices = new ArrayList<>(); @@ -3202,7 +3250,7 @@ public class AudioService extends IAudioService.Stub return status; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#clearPreferredDevicesForCapturePreset(int) */ public int clearPreferredDevicesForCapturePreset(int capturePreset) { super.clearPreferredDevicesForCapturePreset_enforcePermission(); @@ -3218,7 +3266,7 @@ public class AudioService extends IAudioService.Stub return status; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @see AudioManager#getPreferredDevicesForCapturePreset(int) */ @@ -3552,7 +3600,7 @@ public class AudioService extends IAudioService.Stub if (isMuteAdjust && (streamType == AudioSystem.STREAM_VOICE_CALL || streamType == AudioSystem.STREAM_BLUETOOTH_SCO) && - mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid) + mContext.checkPermission(MODIFY_PHONE_STATE, pid, uid) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); @@ -3563,7 +3611,7 @@ public class AudioService extends IAudioService.Stub // make sure that the calling app have the MODIFY_AUDIO_ROUTING permission. if (streamType == AudioSystem.STREAM_ASSISTANT && mContext.checkPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid) + MODIFY_AUDIO_ROUTING, pid, uid) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); @@ -3719,8 +3767,10 @@ public class AudioService extends IAudioService.Stub int newIndex = mStreamStates[streamType].getIndex(device); + int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() : + AudioSystem.STREAM_MUSIC; // Check if volume update should be send to AVRCP - if (streamTypeAlias == AudioSystem.STREAM_MUSIC + if (streamTypeAlias == streamToDriveAbsVol && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { @@ -3994,50 +4044,25 @@ public class AudioService extends IAudioService.Stub } private void enforceModifyAudioRoutingPermission() { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + if (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission"); } } - private void enforceAccessUltrasoundPermission() { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Missing ACCESS_ULTRASOUND permission"); - } - } - - private void enforceQueryStatePermission() { - if (mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Missing QUERY_AUDIO_STATE permissions"); - } - } - private void enforceQueryStateOrModifyRoutingPermission() { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + if (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE) + && mContext.checkCallingOrSelfPermission(QUERY_AUDIO_STATE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "Missing MODIFY_AUDIO_ROUTING or QUERY_AUDIO_STATE permissions"); } } - private void enforceCallAudioInterceptionPermission() { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.CALL_AUDIO_INTERCEPTION) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Missing CALL_AUDIO_INTERCEPTION permission"); - } - } - - @Override @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) /** @see AudioManager#setVolumeGroupVolumeIndex(int, int, int) */ public void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage, String attributionTag) { @@ -4071,9 +4096,7 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) /** @see AudioManager#getVolumeGroupVolumeIndex(int) */ public int getVolumeGroupVolumeIndex(int groupId) { super.getVolumeGroupVolumeIndex_enforcePermission(); @@ -4090,9 +4113,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getVolumeGroupMaxVolumeIndex(int) */ @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) public int getVolumeGroupMaxVolumeIndex(int groupId) { super.getVolumeGroupMaxVolumeIndex_enforcePermission(); synchronized (VolumeStreamState.class) { @@ -4106,9 +4127,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getVolumeGroupMinVolumeIndex(int) */ @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) public int getVolumeGroupMinVolumeIndex(int groupId) { super.getVolumeGroupMinVolumeIndex_enforcePermission(); synchronized (VolumeStreamState.class) { @@ -4122,9 +4141,7 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(anyOf = { - android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED - }) + MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes) * Part of service interface, check permissions and parameters here * Note calling package is for logging purposes only, not to be trusted @@ -4250,7 +4267,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#getLastAudibleVolumeForVolumeGroup(int) */ - @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE) + @android.annotation.EnforcePermission(QUERY_AUDIO_STATE) public int getLastAudibleVolumeForVolumeGroup(int groupId) { super.getLastAudibleVolumeForVolumeGroup_enforcePermission(); synchronized (VolumeStreamState.class) { @@ -4315,16 +4332,14 @@ public class AudioService extends IAudioService.Stub return; } if ((streamType == AudioManager.STREAM_VOICE_CALL) && (index == 0) - && (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + && (mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED)) { Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without" + " MODIFY_PHONE_STATE callingPackage=" + callingPackage); return; } if ((streamType == AudioManager.STREAM_ASSISTANT) - && (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING) + && (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED)) { Log.w(TAG, "Trying to call setStreamVolume() for STREAM_ASSISTANT without" + " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage); @@ -4345,7 +4360,7 @@ public class AudioService extends IAudioService.Stub canChangeMuteAndUpdateController); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND) + @android.annotation.EnforcePermission(Manifest.permission.ACCESS_ULTRASOUND) /** @see AudioManager#isUltrasoundSupported() */ public boolean isUltrasoundSupported() { super.isUltrasoundSupported_enforcePermission(); @@ -4353,8 +4368,8 @@ public class AudioService extends IAudioService.Stub return AudioSystem.isUltrasoundSupported(); } - /** @see AudioManager#isHotwordStreamSupported() */ - @android.annotation.EnforcePermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) + /** @see AudioManager#isHotwordStreamSupported(boolean) */ + @android.annotation.EnforcePermission(CAPTURE_AUDIO_HOTWORD) public boolean isHotwordStreamSupported(boolean lookbackAudio) { super.isHotwordStreamSupported_enforcePermission(); try { @@ -4370,7 +4385,7 @@ public class AudioService extends IAudioService.Stub private boolean canChangeAccessibilityVolume() { synchronized (mAccessibilityServiceUidsLock) { if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.CHANGE_ACCESSIBILITY_VOLUME)) { + Manifest.permission.CHANGE_ACCESSIBILITY_VOLUME)) { return true; } if (mAccessibilityServiceUids != null) { @@ -4547,15 +4562,20 @@ public class AudioService extends IAudioService.Stub + featureSpatialAudioHeadtrackingLowLatency()); pw.println("\tandroid.media.audio.focusFreezeTestApi:" + focusFreezeTestApi()); + pw.println("\tcom.android.media.audio.audioserverPermissions:" + + audioserverPermissions()); pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); - pw.println("\tcom.android.media.audio.setStreamVolumeOrder:" + setStreamVolumeOrder()); pw.println("\tandroid.media.audio.roForegroundAudioControl:" + roForegroundAudioControl()); + pw.println("\tandroid.media.audio.scoManagedByAudio:" + + scoManagedByAudio()); pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:" + vgsVssSyncMuteOrder()); + pw.println("\tcom.android.media.audio.absVolumeIndexFix:" + + absVolumeIndexFix()); } private void dumpAudioMode(PrintWriter pw) { @@ -4751,7 +4771,9 @@ public class AudioService extends IAudioService.Stub } } - if (streamTypeAlias == AudioSystem.STREAM_MUSIC + int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() : + AudioSystem.STREAM_MUSIC; + if (streamTypeAlias == streamToDriveAbsVol && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { @@ -4871,7 +4893,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#forceVolumeControlStream(int) */ public void forceVolumeControlStream(int streamType, IBinder cb) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + if (mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { return; } @@ -5124,7 +5146,7 @@ public class AudioService extends IAudioService.Stub return; } if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( - android.Manifest.permission.CAPTURE_AUDIO_OUTPUT))) { + CAPTURE_AUDIO_OUTPUT))) { Log.w(TAG, "Trying to call forceRemoteSubmixFullVolume() without CAPTURE_AUDIO_OUTPUT"); return; } @@ -5171,8 +5193,7 @@ public class AudioService extends IAudioService.Stub return; } if (userId != UserHandle.getCallingUserId() && - mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - pid, uid) + mContext.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid) != PackageManager.PERMISSION_GRANTED) { return; } @@ -5213,7 +5234,7 @@ public class AudioService extends IAudioService.Stub return mMasterMute.get(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#setMasterMute(boolean, int) */ public void setMasterMute(boolean mute, int flags, String callingPackage, int userId, String attributionTag) { @@ -5249,9 +5270,7 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(anyOf = { - android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED - }) + MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) /** * @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes) */ @@ -5299,12 +5318,12 @@ public class AudioService extends IAudioService.Stub final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID || callingHasAudioSettingsPermission() - || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) + || (mContext.checkCallingPermission(MODIFY_AUDIO_ROUTING) == PackageManager.PERMISSION_GRANTED); return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10; } - @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE) + @android.annotation.EnforcePermission(QUERY_AUDIO_STATE) /** Get last audible volume before stream was muted. */ public int getLastAudibleStreamVolume(int streamType) { super.getLastAudibleStreamVolume_enforcePermission(); @@ -5466,8 +5485,7 @@ public class AudioService extends IAudioService.Stub return; } if (userId != UserHandle.getCallingUserId() && - mContext.checkCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + mContext.checkCallingOrSelfPermission(INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED) { mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission").record(); return; @@ -6057,7 +6075,7 @@ public class AudioService extends IAudioService.Stub } final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; if ((mode == AudioSystem.MODE_IN_CALL || mode == AudioSystem.MODE_CALL_REDIRECT @@ -6204,6 +6222,17 @@ public class AudioService extends IAudioService.Stub setLeAudioVolumeOnModeUpdate(mode, device, streamAlias, index, maxIndex); + synchronized (mCachedAbsVolDrivingStreamsLock) { + mCachedAbsVolDrivingStreams.replaceAll((absDev, stream) -> { + int streamToDriveAbs = getBluetoothContextualVolumeStream(); + if (stream != streamToDriveAbs) { + mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/ + "", /*enabled*/true, streamToDriveAbs); + } + return streamToDriveAbs; + }); + } + // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO // connections not started by the application changing the mode when pid changes mDeviceBroker.postSetModeOwner(mode, pid, uid); @@ -6264,7 +6293,7 @@ public class AudioService extends IAudioService.Stub mModeDispatchers.unregister(dispatcher); } - @android.annotation.EnforcePermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) + @android.annotation.EnforcePermission(CALL_AUDIO_INTERCEPTION) /** @see AudioManager#isPstnCallAudioInterceptable() */ public boolean isPstnCallAudioInterceptable() { @@ -6290,7 +6319,7 @@ public class AudioService extends IAudioService.Stub @Override public void setRttEnabled(boolean rttEnabled) { if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setRttEnabled from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); @@ -6582,8 +6611,7 @@ public class AudioService extends IAudioService.Stub ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) .record(); } - final boolean isPrivileged = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + final boolean isPrivileged = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { @@ -6633,8 +6661,7 @@ public class AudioService extends IAudioService.Stub if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } - final boolean isPrivileged = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + final boolean isPrivileged = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; // for logging only @@ -6701,7 +6728,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#setA2dpSuspended(boolean) */ - @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK) + @android.annotation.EnforcePermission(BLUETOOTH_STACK) public void setA2dpSuspended(boolean enable) { super.setA2dpSuspended_enforcePermission(); final String eventSource = new StringBuilder("setA2dpSuspended(").append(enable) @@ -6711,7 +6738,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#setA2dpSuspended(boolean) */ - @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK) + @android.annotation.EnforcePermission(BLUETOOTH_STACK) public void setLeAudioSuspended(boolean enable) { super.setLeAudioSuspended_enforcePermission(); final String eventSource = new StringBuilder("setLeAudioSuspended(").append(enable) @@ -6816,8 +6843,7 @@ public class AudioService extends IAudioService.Stub mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record(); return; } - final boolean isPrivileged = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + final boolean isPrivileged = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { @@ -6840,8 +6866,7 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("stopBluetoothSco()") .append(") from u/pid:").append(uid).append("/") .append(pid).toString(); - final boolean isPrivileged = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + final boolean isPrivileged = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { @@ -7322,17 +7347,17 @@ public class AudioService extends IAudioService.Stub } private boolean callingOrSelfHasAudioSettingsPermission() { - return mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + return mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_SETTINGS) == PackageManager.PERMISSION_GRANTED; } private boolean callingHasAudioSettingsPermission() { - return mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + return mContext.checkCallingPermission(MODIFY_AUDIO_SETTINGS) == PackageManager.PERMISSION_GRANTED; } private boolean hasAudioSettingsPermission(int uid, int pid) { - return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + return mContext.checkPermission(MODIFY_AUDIO_SETTINGS, pid, uid) == PackageManager.PERMISSION_GRANTED; } @@ -7524,17 +7549,16 @@ public class AudioService extends IAudioService.Stub * @param register Whether the listener is to be registered or unregistered. If false, the * device adopts variable volume behavior. */ - @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.BLUETOOTH_PRIVILEGED }) + @RequiresPermission(anyOf = { MODIFY_AUDIO_ROUTING, BLUETOOTH_PRIVILEGED }) public void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register, IAudioDeviceVolumeDispatcher cb, String packageName, AudioDeviceAttributes device, List<VolumeInfo> volumes, boolean handlesVolumeAdjustment, @AudioManager.AbsoluteDeviceVolumeBehavior int deviceVolumeBehavior) { // verify permissions - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + if (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + && mContext.checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "Missing MODIFY_AUDIO_ROUTING or BLUETOOTH_PRIVILEGED permissions"); @@ -7592,9 +7616,7 @@ public class AudioService extends IAudioService.Stub * @param deviceVolumeBehavior one of the device behaviors */ @android.annotation.EnforcePermission(anyOf = { - android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED - }) + MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) { // verify permissions @@ -7677,9 +7699,7 @@ public class AudioService extends IAudioService.Stub * @return the volume behavior for the device */ @android.annotation.EnforcePermission(anyOf = { - android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.QUERY_AUDIO_STATE, - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED + MODIFY_AUDIO_ROUTING, QUERY_AUDIO_STATE, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) public @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { @@ -7767,7 +7787,7 @@ public class AudioService extends IAudioService.Stub */ private static final byte[] DEFAULT_ARC_AUDIO_DESCRIPTOR = new byte[]{0x09, 0x7f, 0x07}; - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * see AudioManager.setWiredDeviceConnectionState() */ @@ -7877,7 +7897,7 @@ public class AudioService extends IAudioService.Stub public @interface BtProfile {} - @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK) + @android.annotation.EnforcePermission(BLUETOOTH_STACK) /** * See AudioManager.handleBluetoothActiveDeviceChanged(...) */ @@ -7892,7 +7912,8 @@ public class AudioService extends IAudioService.Stub if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK && profile != BluetoothProfile.LE_AUDIO && profile != BluetoothProfile.LE_AUDIO_BROADCAST - && profile != BluetoothProfile.HEARING_AID) { + && profile != BluetoothProfile.HEARING_AID + && !(mDeviceBroker.isScoManagedByAudio() && profile == BluetoothProfile.HEADSET)) { throw new IllegalArgumentException("Illegal BluetoothProfile profile for device " + previousDevice + " -> " + newDevice + ". Got: " + profile); } @@ -8133,6 +8154,10 @@ public class AudioService extends IAudioService.Stub return mAudioVolumeGroup.name(); } + public int getId() { + return mAudioVolumeGroup.getId(); + } + /** * Volume group with non null minimum index are considered as non mutable, thus * bijectivity is broken with potential associated stream type. @@ -8783,24 +8808,30 @@ public class AudioService extends IAudioService.Stub } private int getAbsoluteVolumeIndex(int index) { - /* Special handling for Bluetooth Absolute Volume scenario - * If we send full audio gain, some accessories are too loud even at its lowest - * volume. We are not able to enumerate all such accessories, so here is the - * workaround from phone side. - * Pre-scale volume at lowest volume steps 1 2 and 3. - * For volume step 0, set audio gain to 0 as some accessories won't mute on their end. - */ - if (index == 0) { - // 0% for volume 0 - index = 0; - } else if (!disablePrescaleAbsoluteVolume() && index > 0 && index <= 3) { - // Pre-scale for volume steps 1 2 and 3 - index = (int) (mIndexMax * mPrescaleAbsoluteVolume[index - 1]) / 10; + if (absVolumeIndexFix()) { + // The attenuation is applied in the APM. No need to manipulate the index here + return index; } else { - // otherwise, full gain - index = (mIndexMax + 5) / 10; + /* Special handling for Bluetooth Absolute Volume scenario + * If we send full audio gain, some accessories are too loud even at its lowest + * volume. We are not able to enumerate all such accessories, so here is the + * workaround from phone side. + * Pre-scale volume at lowest volume steps 1 2 and 3. + * For volume step 0, set audio gain to 0 as some accessories won't mute on their + * end. + */ + if (index == 0) { + // 0% for volume 0 + index = 0; + } else if (!disablePrescaleAbsoluteVolume() && index > 0 && index <= 3) { + // Pre-scale for volume steps 1 2 and 3 + index = (int) (mIndexMax * mPrescaleAbsoluteVolume[index - 1]) / 10; + } else { + // otherwise, full gain + index = (mIndexMax + 5) / 10; + } + return index; } - return index; } private void setStreamVolumeIndex(int index, int device) { @@ -8811,6 +8842,11 @@ public class AudioService extends IAudioService.Stub && !isFullyMuted()) { index = 1; } + + if (DEBUG_VOL) { + Log.d(TAG, "setStreamVolumeIndexAS(" + mStreamType + ", " + index + ", " + device + + ")"); + } mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } @@ -8822,14 +8858,24 @@ public class AudioService extends IAudioService.Stub } else if (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device) || AudioSystem.isLeAudioDeviceType(device)) { - index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); + // do not change the volume logic for dynamic abs behavior devices like HDMI + if (absVolumeIndexFix() && isAbsoluteVolumeDevice(device)) { + index = getAbsoluteVolumeIndex((mIndexMax + 5) / 10); + } else { + index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10); + } } else if (isFullVolumeDevice(device)) { index = (mIndexMax + 5)/10; } else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) { - index = (mIndexMax + 5)/10; + if (absVolumeIndexFix()) { + index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10); + } else { + index = (mIndexMax + 5) / 10; + } } else { index = (getIndex(device) + 5)/10; } + setStreamVolumeIndex(index, device); } @@ -8847,11 +8893,22 @@ public class AudioService extends IAudioService.Stub || isA2dpAbsoluteVolumeDevice(device) || AudioSystem.isLeAudioDeviceType(device)) { isAbsoluteVolume = true; - index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); + // do not change the volume logic for dynamic abs behavior devices + // like HDMI + if (absVolumeIndexFix() && isAbsoluteVolumeDevice(device)) { + index = getAbsoluteVolumeIndex((mIndexMax + 5) / 10); + } else { + index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10); + } } else if (isFullVolumeDevice(device)) { index = (mIndexMax + 5)/10; } else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) { - index = (mIndexMax + 5)/10; + if (absVolumeIndexFix()) { + isAbsoluteVolume = true; + index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10); + } else { + index = (mIndexMax + 5) / 10; + } } else { index = (mIndexMap.valueAt(i) + 5)/10; } @@ -9848,6 +9905,27 @@ public class AudioService extends IAudioService.Stub /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean support) { mAvrcpAbsVolSupported = support; + if (absVolumeIndexFix()) { + int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; + synchronized (mCachedAbsVolDrivingStreamsLock) { + mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> { + if (stream != null && !mAvrcpAbsVolSupported) { + mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/ + "", /*enabled*/false, AudioSystem.DEVICE_NONE); + return null; + } + // For A2DP and AVRCP we need to set the driving stream based on the + // BT contextual stream. Hence, we need to make sure in adjustStreamVolume + // and setStreamVolume that the driving abs volume stream is consistent. + int streamToDriveAbs = getBluetoothContextualVolumeStream(); + if (stream == null || stream != streamToDriveAbs) { + mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/ + "", /*enabled*/true, streamToDriveAbs); + } + return streamToDriveAbs; + }); + } + } sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, mStreamStates[AudioSystem.STREAM_MUSIC], 0); @@ -10153,8 +10231,8 @@ public class AudioService extends IAudioService.Stub if (AudioAttributes.isSystemUsage(usage)) { if ((usage == AudioAttributes.USAGE_CALL_ASSISTANT && (audioAttributes.getAllFlags() & AudioAttributes.FLAG_CALL_REDIRECTION) != 0 - && callerHasPermission(Manifest.permission.CALL_AUDIO_INTERCEPTION)) - || callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)) { + && callerHasPermission(CALL_AUDIO_INTERCEPTION)) + || callerHasPermission(MODIFY_AUDIO_ROUTING)) { if (!isSupportedSystemUsage(usage)) { throw new IllegalArgumentException( "Unsupported usage " + AudioAttributes.usageToString(usage)); @@ -10172,8 +10250,8 @@ public class AudioService extends IAudioService.Stub && ((usage == AudioAttributes.USAGE_CALL_ASSISTANT && (audioAttributes.getAllFlags() & AudioAttributes.FLAG_CALL_REDIRECTION) != 0 - && callerHasPermission(Manifest.permission.CALL_AUDIO_INTERCEPTION)) - || callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)); + && callerHasPermission(CALL_AUDIO_INTERCEPTION)) + || callerHasPermission(MODIFY_AUDIO_ROUTING)); } return true; } @@ -10204,7 +10282,7 @@ public class AudioService extends IAudioService.Stub if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) { if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) { if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE)) { + MODIFY_PHONE_STATE)) { final String reason = "Invalid permission to (un)lock audio focus"; Log.e(TAG, reason, new Exception()); mmi.set(MediaMetrics.Property.EARLY_RETURN, reason) @@ -10236,10 +10314,9 @@ public class AudioService extends IAudioService.Stub // does caller have system privileges to bypass HardeningEnforcer boolean permissionOverridesCheck = false; - if ((mContext.checkCallingOrSelfPermission( - Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + if ((mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) == PackageManager.PERMISSION_GRANTED) - || (mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) + || (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) == PackageManager.PERMISSION_GRANTED)) { permissionOverridesCheck = true; } else if (uid < UserHandle.AID_APP_START) { @@ -10373,7 +10450,7 @@ public class AudioService extends IAudioService.Stub * such as another freeze currently used. */ @Override - @EnforcePermission("android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") + @EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean enterAudioFocusFreezeForTest(IBinder cb, int[] exemptedUids) { super.enterAudioFocusFreezeForTest_enforcePermission(); Objects.requireNonNull(exemptedUids); @@ -10389,7 +10466,7 @@ public class AudioService extends IAudioService.Stub * such as the freeze already having ended, or not started. */ @Override - @EnforcePermission("android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") + @EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean exitAudioFocusFreezeForTest(IBinder cb) { super.exitAudioFocusFreezeForTest_enforcePermission(); Objects.requireNonNull(cb); @@ -10435,8 +10512,7 @@ public class AudioService extends IAudioService.Stub private static final boolean SPATIAL_AUDIO_ENABLED_DEFAULT = true; private void enforceModifyDefaultAudioEffectsPermission() { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + if (mContext.checkCallingOrSelfPermission(MODIFY_DEFAULT_AUDIO_EFFECTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing MODIFY_DEFAULT_AUDIO_EFFECTS permission"); } @@ -10460,7 +10536,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.isAvailable(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#isAvailableForDevice(AudioDeviceAttributes) */ public boolean isSpatializerAvailableForDevice(@NonNull AudioDeviceAttributes device) { super.isSpatializerAvailableForDevice_enforcePermission(); @@ -10468,7 +10544,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.isAvailableForDevice(Objects.requireNonNull(device)); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#hasHeadTracker(AudioDeviceAttributes) */ public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) { super.hasHeadTracker_enforcePermission(); @@ -10476,7 +10552,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.hasHeadTracker(Objects.requireNonNull(device)); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setHeadTrackerEnabled(boolean, AudioDeviceAttributes) */ public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) { super.setHeadTrackerEnabled_enforcePermission(); @@ -10484,7 +10560,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setHeadTrackerEnabled(enabled, Objects.requireNonNull(device)); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#isHeadTrackerEnabled(AudioDeviceAttributes) */ public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) { super.isHeadTrackerEnabled_enforcePermission(); @@ -10497,7 +10573,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.isHeadTrackerAvailable(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setSpatializerEnabled(boolean) */ public void setSpatializerEnabled(boolean enabled) { super.setSpatializerEnabled_enforcePermission(); @@ -10527,7 +10603,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.unregisterStateCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ public void registerSpatializerHeadTrackingCallback( @NonNull ISpatializerHeadTrackingModeCallback cb) { @@ -10537,7 +10613,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.registerHeadTrackingModeCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ public void unregisterSpatializerHeadTrackingCallback( @NonNull ISpatializerHeadTrackingModeCallback cb) { @@ -10554,7 +10630,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.registerHeadTrackerAvailableCallback(cb, register); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */ public void registerHeadToSoundstagePoseCallback( @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { @@ -10564,7 +10640,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */ public void unregisterHeadToSoundstagePoseCallback( @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { @@ -10574,7 +10650,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getSpatializerCompatibleAudioDevices() */ public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() { super.getSpatializerCompatibleAudioDevices_enforcePermission(); @@ -10582,7 +10658,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getCompatibleAudioDevices(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#addSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */ public void addSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { super.addSpatializerCompatibleAudioDevice_enforcePermission(); @@ -10591,7 +10667,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.addCompatibleAudioDevice(ada); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#removeSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */ public void removeSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { super.removeSpatializerCompatibleAudioDevice_enforcePermission(); @@ -10600,7 +10676,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.removeCompatibleAudioDevice(ada); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getSupportedHeadTrackingModes() */ public int[] getSupportedHeadTrackingModes() { super.getSupportedHeadTrackingModes_enforcePermission(); @@ -10608,7 +10684,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getSupportedHeadTrackingModes(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getHeadTrackingMode() */ public int getActualHeadTrackingMode() { super.getActualHeadTrackingMode_enforcePermission(); @@ -10616,7 +10692,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getActualHeadTrackingMode(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getDesiredHeadTrackingMode() */ public int getDesiredHeadTrackingMode() { super.getDesiredHeadTrackingMode_enforcePermission(); @@ -10624,7 +10700,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getDesiredHeadTrackingMode(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setGlobalTransform */ public void setSpatializerGlobalTransform(@NonNull float[] transform) { super.setSpatializerGlobalTransform_enforcePermission(); @@ -10633,7 +10709,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setGlobalTransform(transform); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#recenterHeadTracker() */ public void recenterHeadTracker() { super.recenterHeadTracker_enforcePermission(); @@ -10641,7 +10717,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.recenterHeadTracker(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setDesiredHeadTrackingMode */ public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { super.setDesiredHeadTrackingMode_enforcePermission(); @@ -10657,7 +10733,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setDesiredHeadTrackingMode(mode); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setEffectParameter */ public void setSpatializerParameter(int key, @NonNull byte[] value) { super.setSpatializerParameter_enforcePermission(); @@ -10666,7 +10742,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setEffectParameter(key, value); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getEffectParameter */ public void getSpatializerParameter(int key, @NonNull byte[] value) { super.getSpatializerParameter_enforcePermission(); @@ -10675,7 +10751,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.getEffectParameter(key, value); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getOutput */ public int getSpatializerOutput() { super.getSpatializerOutput_enforcePermission(); @@ -10683,7 +10759,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getOutput(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setOnSpatializerOutputChangedListener */ public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) { super.registerSpatializerOutputCallback_enforcePermission(); @@ -10692,7 +10768,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.registerSpatializerOutputCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#clearOnSpatializerOutputChangedListener */ public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) { super.unregisterSpatializerOutputCallback_enforcePermission(); @@ -10739,7 +10815,7 @@ public class AudioService extends IAudioService.Stub private boolean isBluetoothPrividged() { return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.BLUETOOTH_CONNECT) + Manifest.permission.BLUETOOTH_CONNECT) || Binder.getCallingUid() == Process.SYSTEM_UID; } @@ -10946,7 +11022,7 @@ public class AudioService extends IAudioService.Stub }); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#getMutingExpectedDevice */ public @Nullable AudioDeviceAttributes getMutingExpectedDevice() { super.getMutingExpectedDevice_enforcePermission(); @@ -10997,7 +11073,7 @@ public class AudioService extends IAudioService.Stub final RemoteCallbackList<IMuteAwaitConnectionCallback> mMuteAwaitConnectionDispatchers = new RemoteCallbackList<IMuteAwaitConnectionCallback>(); - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#registerMuteAwaitConnectionCallback */ public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb, boolean register) { @@ -11172,7 +11248,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.REMOTE_AUDIO_PLAYBACK) + @android.annotation.EnforcePermission(Manifest.permission.REMOTE_AUDIO_PLAYBACK) @Override public void setRingtonePlayer(IRingtonePlayer player) { setRingtonePlayer_enforcePermission(); @@ -11373,7 +11449,6 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) - @AudioDeviceCategory public boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) { super.isBluetoothAudioDeviceCategoryFixed_enforcePermission(); if (!automaticBtDeviceType()) { @@ -11867,6 +11942,45 @@ public class AudioService extends IAudioService.Stub private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE + MediaMetrics.SEPARATOR; + private static AudioServerPermissionProvider initializeAudioServerPermissionProvider( + Context context, AudioPolicyFacade audioPolicy, Executor audioserverExecutor) { + Collection<PackageState> packageStates = null; + try (PackageManagerLocal.UnfilteredSnapshot snapshot = + LocalManagerRegistry.getManager(PackageManagerLocal.class) + .withUnfilteredSnapshot()) { + packageStates = snapshot.getPackageStates().values(); + } + var provider = new AudioServerPermissionProvider(packageStates); + audioPolicy.registerOnStartTask(() -> { + provider.onServiceStart(audioPolicy.getPermissionController()); + }); + + // Set up event listeners + IntentFilter packageUpdateFilter = new IntentFilter(); + packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED); + packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED); + packageUpdateFilter.addDataScheme("package"); + + context.registerReceiverForAllUsers(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + String pkgName = intent.getData().getEncodedSchemeSpecificPart(); + int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); + if (intent.getBooleanExtra(EXTRA_REPLACING, false) || + intent.getBooleanExtra(EXTRA_ARCHIVAL, false)) return; + if (action.equals(ACTION_PACKAGE_ADDED)) { + audioserverExecutor.execute(() -> + provider.onModifyPackageState(uid, pkgName, false /* isRemoved */)); + } else if (action.equals(ACTION_PACKAGE_REMOVED)) { + audioserverExecutor.execute(() -> + provider.onModifyPackageState(uid, pkgName, true /* isRemoved */)); + } + } + }, packageUpdateFilter, null, null); // main thread is fine, since dispatch on executor + return provider; + } + // Inform AudioFlinger of our device's low RAM attribute private static void readAndSetLowRamDevice() { @@ -11889,7 +12003,7 @@ public class AudioService extends IAudioService.Stub } private void enforceVolumeController(String action) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + mContext.enforceCallingOrSelfPermission(Manifest.permission.STATUS_BAR_SERVICE, "Only SystemUI can " + action); } @@ -12393,8 +12507,8 @@ public class AudioService extends IAudioService.Stub } if (requireCaptureAudioOrMediaOutputPerm - && !callerHasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT) - && !callerHasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)) { + && !callerHasPermission(CAPTURE_MEDIA_OUTPUT) + && !callerHasPermission(CAPTURE_AUDIO_OUTPUT)) { Log.e(TAG, "Privileged audio capture requires CAPTURE_MEDIA_OUTPUT or " + "CAPTURE_AUDIO_OUTPUT system permission"); return false; @@ -12402,7 +12516,7 @@ public class AudioService extends IAudioService.Stub if (voiceCommunicationCaptureMixes != null && voiceCommunicationCaptureMixes.size() > 0) { if (!callerHasPermission( - android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) { + Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) { Log.e(TAG, "Audio capture for voice communication requires " + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission"); return false; @@ -12419,13 +12533,12 @@ public class AudioService extends IAudioService.Stub } if (requireModifyRouting - && !callerHasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)) { + && !callerHasPermission(MODIFY_AUDIO_ROUTING)) { Log.e(TAG, "Can not capture audio without MODIFY_AUDIO_ROUTING"); return false; } - if (requireCallAudioInterception - && !callerHasPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)) { + if (requireCallAudioInterception && !callerHasPermission(CALL_AUDIO_INTERCEPTION)) { Log.e(TAG, "Can not capture audio without CALL_AUDIO_INTERCEPTION"); return false; } @@ -12538,7 +12651,7 @@ public class AudioService extends IAudioService.Stub // permission check final boolean hasPermissionForPolicy = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); if (!hasPermissionForPolicy) { Slog.w(TAG, errorMsg + " for pid " + + Binder.getCallingPid() + " / uid " @@ -12622,7 +12735,7 @@ public class AudioService extends IAudioService.Stub * @return {@link AudioManager#SUCCESS} iff the mixing rules were updated successfully, * {@link AudioManager#ERROR} otherwise. */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public int updateMixingRulesForPolicy( @NonNull AudioMix[] mixesToUpdate, @NonNull AudioMixingRule[] updatedMixingRules, @@ -12750,7 +12863,7 @@ public class AudioService extends IAudioService.Stub return AudioManager.SUCCESS; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioPolicy#getFocusStack() */ public List<AudioFocusInfo> getFocusStack() { super.getFocusStack_enforcePermission(); @@ -12776,8 +12889,7 @@ public class AudioService extends IAudioService.Stub /** * see {@link AudioPolicy#setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)} */ - @android.annotation.EnforcePermission( - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int setFadeManagerConfigurationForFocusLoss( @NonNull FadeManagerConfiguration fmcForFocusLoss) { super.setFadeManagerConfigurationForFocusLoss_enforcePermission(); @@ -12793,8 +12905,7 @@ public class AudioService extends IAudioService.Stub /** * see {@link AudioPolicy#clearFadeManagerConfigurationForFocusLoss()} */ - @android.annotation.EnforcePermission( - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int clearFadeManagerConfigurationForFocusLoss() { super.clearFadeManagerConfigurationForFocusLoss_enforcePermission(); ensureFadeManagerConfigIsEnabled(); @@ -12805,8 +12916,7 @@ public class AudioService extends IAudioService.Stub /** * see {@link AudioPolicy#getFadeManagerConfigurationForFocusLoss()} */ - @android.annotation.EnforcePermission( - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() { super.getFadeManagerConfigurationForFocusLoss_enforcePermission(); ensureFadeManagerConfigIsEnabled(); @@ -12967,7 +13077,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#supportsBluetoothVariableLatency() */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public boolean supportsBluetoothVariableLatency() { super.supportsBluetoothVariableLatency_enforcePermission(); try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { @@ -12976,7 +13086,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#setBluetoothVariableLatencyEnabled(boolean) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public void setBluetoothVariableLatencyEnabled(boolean enabled) { super.setBluetoothVariableLatencyEnabled_enforcePermission(); try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { @@ -12985,7 +13095,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#isBluetoothVariableLatencyEnabled(boolean) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public boolean isBluetoothVariableLatencyEnabled() { super.isBluetoothVariableLatencyEnabled_enforcePermission(); try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { @@ -13072,7 +13182,7 @@ public class AudioService extends IAudioService.Stub public void registerRecordingCallback(IRecordingConfigDispatcher rcdb) { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); mRecordMonitor.registerRecordingCallback(rcdb, isPrivileged); } @@ -13083,7 +13193,7 @@ public class AudioService extends IAudioService.Stub public List<AudioRecordingConfiguration> getActiveRecordingConfigurations() { final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID || (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged); } @@ -13119,7 +13229,7 @@ public class AudioService extends IAudioService.Stub public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); mPlaybackMonitor.registerPlaybackCallback(pcdb, isPrivileged); } @@ -13130,7 +13240,7 @@ public class AudioService extends IAudioService.Stub public List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); return mPlaybackMonitor.getActivePlaybackConfigurations(isPrivileged); } @@ -13721,8 +13831,7 @@ public class AudioService extends IAudioService.Stub * see {@link AudioManager#dispatchAudioFocusChangeWithFade(AudioFocusInfo, int, AudioPolicy, * List, FadeManagerConfiguration)} */ - @android.annotation.EnforcePermission( - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb, List<AudioFocusInfo> otherActiveAfis, FadeManagerConfiguration transientFadeMgrConfig) { @@ -13759,8 +13868,7 @@ public class AudioService extends IAudioService.Stub /** * @see AudioManager#shouldNotificationSoundPlay(AudioAttributes) */ - @android.annotation.EnforcePermission( - android.Manifest.permission.QUERY_AUDIO_STATE) + @android.annotation.EnforcePermission(QUERY_AUDIO_STATE) public boolean shouldNotificationSoundPlay(@NonNull final AudioAttributes aa) { super.shouldNotificationSoundPlay_enforcePermission(); Objects.requireNonNull(aa); @@ -13820,12 +13928,10 @@ public class AudioService extends IAudioService.Stub new HashMap<IBinder, AsdProxy>(); private void checkMonitorAudioServerStatePermission() { - if (!(mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) == - PackageManager.PERMISSION_GRANTED || - mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING) == - PackageManager.PERMISSION_GRANTED)) { + if (!(mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED + || mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) + == PackageManager.PERMISSION_GRANTED)) { throw new SecurityException("Not allowed to monitor audioserver state"); } } @@ -13920,7 +14026,7 @@ public class AudioService extends IAudioService.Stub AudioSystem.setAudioHalPids(pidsArray); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) //====================== // Multi Audio Focus //====================== @@ -13961,7 +14067,7 @@ public class AudioService extends IAudioService.Stub * or the delay is not in range of {@link #getMaxAdditionalOutputDeviceDelay()}. */ @Override - //@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + //@RequiresPermission(MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay( @NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) { Objects.requireNonNull(device, "device must not be null"); @@ -14034,7 +14140,7 @@ public class AudioService extends IAudioService.Stub return delayMillis; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#addAssistantServicesUids(int []) */ @Override public void addAssistantServicesUids(int [] assistantUids) { @@ -14047,7 +14153,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#removeAssistantServicesUids(int []) */ @Override public void removeAssistantServicesUids(int [] assistantUids) { @@ -14059,7 +14165,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#getAssistantServicesUids() */ @Override public int[] getAssistantServicesUids() { @@ -14072,7 +14178,7 @@ public class AudioService extends IAudioService.Stub return assistantUids; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#setActiveAssistantServiceUids(int []) */ @Override public void setActiveAssistantServiceUids(int [] activeAssistantUids) { @@ -14085,7 +14191,7 @@ public class AudioService extends IAudioService.Stub updateActiveAssistantServiceUids(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#getActiveAssistantServiceUids() */ @Override public int[] getActiveAssistantServiceUids() { diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 7202fa286453..7f4bc74bd59e 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -598,6 +598,21 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, } /** + * Same as {@link AudioSystem#setDeviceAbsoluteVolumeEnabled(int, String, boolean, int)} + * @param nativeDeviceType the internal device type for which absolute volume is + * enabled/disabled + * @param address the address of the device for which absolute volume is enabled/disabled + * @param enabled whether the absolute volume is enabled/disabled + * @param streamToDriveAbs the stream that is controlling the absolute volume + * @return status of indicating the success of this operation + */ + public int setDeviceAbsoluteVolumeEnabled(int nativeDeviceType, @NonNull String address, + boolean enabled, int streamToDriveAbs) { + return AudioSystem.setDeviceAbsoluteVolumeEnabled(nativeDeviceType, address, enabled, + streamToDriveAbs); + } + + /** * Same as {@link AudioSystem#registerPolicyMixes(ArrayList, boolean)} * @param mixes * @param register diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 07daecdfc9f6..6bb3eb1c3078 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -94,14 +94,14 @@ public class BtHelper { private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices = new HashMap<>(); - private @Nullable BluetoothHearingAid mHearingAid; + private @Nullable BluetoothHearingAid mHearingAid = null; - private @Nullable BluetoothLeAudio mLeAudio; + private @Nullable BluetoothLeAudio mLeAudio = null; private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig; // Reference to BluetoothA2dp to query for AbsoluteVolume. - private @Nullable BluetoothA2dp mA2dp; + private @Nullable BluetoothA2dp mA2dp = null; private @Nullable BluetoothCodecConfig mA2dpCodecConfig; @@ -401,50 +401,67 @@ public class BtHelper { private void onScoAudioStateChanged(int state) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; - switch (state) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL - && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } else if (mDeviceBroker.isBluetoothScoRequested()) { - // broadcast intent if the connection was initated by AudioService + if (mDeviceBroker.isScoManagedByAudio()) { + switch (state) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; broadcast = true; - } - mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: - mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - // There are two cases where we want to immediately reconnect audio: - // 1) If a new start request was received while disconnecting: this was - // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. - // 2) If audio was connected then disconnected via Bluetooth APIs and - // we still have pending activation requests by apps: this is indicated by - // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested. - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null - && connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING; + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + broadcast = true; + break; + default: + break; + } + } else { + switch (state) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } else if (mDeviceBroker.isBluetoothScoRequested()) { + // broadcast intent if the connection was initated by AudioService broadcast = true; - break; } - } - if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) { - broadcast = true; - } - mScoAudioState = SCO_STATE_INACTIVE; - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTING: - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL - && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - break; - default: - break; + mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + // There are two cases where we want to immediately reconnect audio: + // 1) If a new start request was received while disconnecting: this was + // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. + // 2) If audio was connected then disconnected via Bluetooth APIs and + // we still have pending activation requests by apps: this is indicated by + // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested. + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null + && connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING; + broadcast = true; + break; + } + } + if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) { + broadcast = true; + } + mScoAudioState = SCO_STATE_INACTIVE; + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + break; + default: + break; + } } if (broadcast) { broadcastScoConnectionState(scoAudioState); @@ -454,7 +471,6 @@ public class BtHelper { newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); sendStickyBroadcastToAll(newIntent); } - } /** * @@ -577,7 +593,11 @@ public class BtHelper { mHearingAid = null; break; case BluetoothProfile.LE_AUDIO: + if (mLeAudio != null && mLeAudioCallback != null) { + mLeAudio.unregisterCallback(mLeAudioCallback); + } mLeAudio = null; + mLeAudioCallback = null; mLeAudioCodecConfig = null; break; case BluetoothProfile.LE_AUDIO_BROADCAST: @@ -596,8 +616,6 @@ public class BtHelper { // BluetoothLeAudio callback used to update the list of addresses in the same group as a // connected LE Audio device - MyLeAudioCallback mLeAudioCallback = null; - class MyLeAudioCallback implements BluetoothLeAudio.Callback { @Override public void onCodecConfigChanged(int groupId, @@ -620,6 +638,8 @@ public class BtHelper { } } + MyLeAudioCallback mLeAudioCallback = null; + // @GuardedBy("mDeviceBroker.mSetModeLock") @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { @@ -635,18 +655,28 @@ public class BtHelper { onHeadsetProfileConnected((BluetoothHeadset) proxy); return; case BluetoothProfile.A2DP: + if (((BluetoothA2dp) proxy).equals(mA2dp)) { + return; + } mA2dp = (BluetoothA2dp) proxy; break; case BluetoothProfile.HEARING_AID: + if (((BluetoothHearingAid) proxy).equals(mHearingAid)) { + return; + } mHearingAid = (BluetoothHearingAid) proxy; break; case BluetoothProfile.LE_AUDIO: - if (mLeAudio == null) { - mLeAudioCallback = new MyLeAudioCallback(); - ((BluetoothLeAudio) proxy).registerCallback( - mContext.getMainExecutor(), mLeAudioCallback); + if (((BluetoothLeAudio) proxy).equals(mLeAudio)) { + return; + } + if (mLeAudio != null && mLeAudioCallback != null) { + mLeAudio.unregisterCallback(mLeAudioCallback); } mLeAudio = (BluetoothLeAudio) proxy; + mLeAudioCallback = new MyLeAudioCallback(); + mLeAudio.registerCallback( + mContext.getMainExecutor(), mLeAudioCallback); break; case BluetoothProfile.A2DP_SINK: case BluetoothProfile.LE_AUDIO_BROADCAST: diff --git a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java index 37b812685a3d..09701e49a8ac 100644 --- a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java +++ b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java @@ -16,100 +16,68 @@ package com.android.server.audio; -import android.annotation.NonNull; import android.annotation.Nullable; import android.media.IAudioPolicyService; -import android.media.permission.ClearCallingIdentityContext; -import android.media.permission.SafeCloseable; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.Log; -import com.android.internal.annotations.GuardedBy; +import com.android.media.permission.INativePermissionController; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Function; /** - * Default implementation of a facade to IAudioPolicyManager which fulfills AudioService - * dependencies. This forwards calls as-is to IAudioPolicyManager. - * Public methods throw IllegalStateException if AudioPolicy is not initialized/available + * Default implementation of a facade to IAudioPolicyService which fulfills AudioService + * dependencies. This forwards calls as-is to IAudioPolicyService. */ -public class DefaultAudioPolicyFacade implements AudioPolicyFacade, IBinder.DeathRecipient { +public class DefaultAudioPolicyFacade implements AudioPolicyFacade { - private static final String TAG = "DefaultAudioPolicyFacade"; private static final String AUDIO_POLICY_SERVICE_NAME = "media.audio_policy"; - private final Object mServiceLock = new Object(); - @GuardedBy("mServiceLock") - private IAudioPolicyService mAudioPolicy; + private final ServiceHolder<IAudioPolicyService> mServiceHolder; - public DefaultAudioPolicyFacade() { - try { - getAudioPolicyOrInit(); - } catch (IllegalStateException e) { - // Log and suppress this exception, we may be able to connect later - Log.e(TAG, "Failed to initialize APM connection", e); - } + /** + * @param e - Executor for service start tasks + */ + public DefaultAudioPolicyFacade(Executor e) { + mServiceHolder = + new ServiceHolder( + AUDIO_POLICY_SERVICE_NAME, + (Function<IBinder, IAudioPolicyService>) + IAudioPolicyService.Stub::asInterface, + e); + mServiceHolder.registerOnStartTask(i -> Binder.allowBlocking(i.asBinder())); } @Override public boolean isHotwordStreamSupported(boolean lookbackAudio) { - IAudioPolicyService ap = getAudioPolicyOrInit(); - try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + IAudioPolicyService ap = mServiceHolder.waitForService(); + try { return ap.isHotwordStreamSupported(lookbackAudio); } catch (RemoteException e) { - resetServiceConnection(ap.asBinder()); - throw new IllegalStateException(e); + mServiceHolder.attemptClear(ap.asBinder()); + throw new IllegalStateException(); } } @Override - public void binderDied() { - Log.wtf(TAG, "Unexpected binderDied without IBinder object"); - } - - @Override - public void binderDied(@NonNull IBinder who) { - resetServiceConnection(who); - } - - private void resetServiceConnection(@Nullable IBinder deadAudioPolicy) { - synchronized (mServiceLock) { - if (mAudioPolicy != null && mAudioPolicy.asBinder().equals(deadAudioPolicy)) { - mAudioPolicy.asBinder().unlinkToDeath(this, 0); - mAudioPolicy = null; - } - } - } - - private @Nullable IAudioPolicyService getAudioPolicy() { - synchronized (mServiceLock) { - return mAudioPolicy; + public @Nullable INativePermissionController getPermissionController() { + IAudioPolicyService ap = mServiceHolder.checkService(); + if (ap == null) return null; + try { + var res = Objects.requireNonNull(ap.getPermissionController()); + Binder.allowBlocking(res.asBinder()); + return res; + } catch (RemoteException e) { + mServiceHolder.attemptClear(ap.asBinder()); + return null; } } - /* - * Does not block. - * @throws IllegalStateException for any failed connection - */ - private @NonNull IAudioPolicyService getAudioPolicyOrInit() { - synchronized (mServiceLock) { - if (mAudioPolicy != null) { - return mAudioPolicy; - } - // Do not block while attempting to connect to APM. Defer to caller. - IAudioPolicyService ap = IAudioPolicyService.Stub.asInterface( - ServiceManager.checkService(AUDIO_POLICY_SERVICE_NAME)); - if (ap == null) { - throw new IllegalStateException(TAG + ": Unable to connect to AudioPolicy"); - } - try { - ap.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - throw new IllegalStateException( - TAG + ": Unable to link deathListener to AudioPolicy", e); - } - mAudioPolicy = ap; - return mAudioPolicy; - } + @Override + public void registerOnStartTask(Runnable task) { + mServiceHolder.registerOnStartTask(unused -> task.run()); } } diff --git a/services/core/java/com/android/server/audio/ServiceHolder.java b/services/core/java/com/android/server/audio/ServiceHolder.java new file mode 100644 index 000000000000..e2588fb4fdb1 --- /dev/null +++ b/services/core/java/com/android/server/audio/ServiceHolder.java @@ -0,0 +1,219 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.audio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.IInterface; +import android.os.IServiceCallback; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Manages a remote service which can start and stop. Allows clients to add tasks to run when the + * remote service starts or dies. + * + * <p>Example usage should look something like: + * + * <pre> + * var service = mServiceHolder.checkService(); + * if (service == null) handleFailure(); + * try { + * service.foo(); + * } catch (RemoteException e) { + * mServiceHolder.attemptClear(service.asBinder()); + * handleFailure(); + * } + * </pre> + */ +public class ServiceHolder<I extends IInterface> implements IBinder.DeathRecipient { + + private final String mTag; + private final String mServiceName; + private final Function<? super IBinder, ? extends I> mCastFunction; + private final Executor mExecutor; + private final ServiceProviderFacade mServiceProvider; + + private final AtomicReference<I> mService = new AtomicReference(); + private final Set<Consumer<I>> mOnStartTasks = ConcurrentHashMap.newKeySet(); + private final Set<Consumer<I>> mOnDeathTasks = ConcurrentHashMap.newKeySet(); + + private final IServiceCallback mServiceListener = + new IServiceCallback.Stub() { + @Override + public void onRegistration(String name, IBinder binder) { + onServiceInited(binder); + } + }; + + // For test purposes + public static interface ServiceProviderFacade { + public void registerForNotifications(String name, IServiceCallback listener); + + public IBinder checkService(String name); + + public IBinder waitForService(String name); + } + + public ServiceHolder( + @NonNull String serviceName, + @NonNull Function<? super IBinder, ? extends I> castFunction, + @NonNull Executor executor) { + this( + serviceName, + castFunction, + executor, + new ServiceProviderFacade() { + @Override + public void registerForNotifications(String name, IServiceCallback listener) { + try { + ServiceManager.registerForNotifications(name, listener); + } catch (RemoteException e) { + throw new IllegalStateException("ServiceManager died!!", e); + } + } + + @Override + public IBinder checkService(String name) { + return ServiceManager.checkService(name); + } + + @Override + public IBinder waitForService(String name) { + return ServiceManager.waitForService(name); + } + }); + } + + public ServiceHolder( + @NonNull String serviceName, + @NonNull Function<? super IBinder, ? extends I> castFunction, + @NonNull Executor executor, + @NonNull ServiceProviderFacade provider) { + mServiceName = Objects.requireNonNull(serviceName); + mCastFunction = Objects.requireNonNull(castFunction); + mExecutor = Objects.requireNonNull(executor); + mServiceProvider = Objects.requireNonNull(provider); + mTag = "ServiceHolder: " + serviceName; + mServiceProvider.registerForNotifications(mServiceName, mServiceListener); + } + + /** + * Add tasks to run when service becomes available. Ran on the executor provided at + * construction. Note, for convenience, if the service is already connected, the task is + * immediately run. + */ + public void registerOnStartTask(Consumer<I> task) { + mOnStartTasks.add(task); + I i; + if ((i = mService.get()) != null) { + mExecutor.execute(() -> task.accept(i)); + } + } + + public void unregisterOnStartTask(Consumer<I> task) { + mOnStartTasks.remove(task); + } + + /** + * Add tasks to run when service goes down. Ran on the executor provided at construction. Should + * be called before getService to avoid dropping a death notification. + */ + public void registerOnDeathTask(Consumer<I> task) { + mOnDeathTasks.add(task); + } + + public void unregisterOnDeathTask(Consumer<I> task) { + mOnDeathTasks.remove(task); + } + + @Override + public void binderDied(@NonNull IBinder who) { + attemptClear(who); + } + + @Override + public void binderDied() { + throw new AssertionError("Wrong binderDied called, this should never happen"); + } + + /** + * Notify the holder that the service has gone done, usually in response to a RemoteException. + * Equivalent to receiving a binder death notification. + */ + public void attemptClear(IBinder who) { + // Possibly prone to weird races, resulting in spurious dead/revive, + // but that should be fine. + var current = mService.get(); + if (current != null + && Objects.equals(current.asBinder(), who) + && mService.compareAndSet(current, null)) { + who.unlinkToDeath(this, 0); + for (var r : mOnDeathTasks) { + mExecutor.execute(() -> r.accept(current)); + } + } + } + + /** Get the service, without blocking. Can trigger start tasks, on the provided executor. */ + public @Nullable I checkService() { + var s = mService.get(); + if (s != null) return s; + IBinder registered = mServiceProvider.checkService(mServiceName); + if (registered == null) return null; + return onServiceInited(registered); + } + + /** Get the service, but block. Can trigger start tasks, on the provided executor. */ + public @NonNull I waitForService() { + var s = mService.get(); + return (s != null) ? s : onServiceInited(mServiceProvider.waitForService(mServiceName)); + } + + /* + * Called when the native service is initialized. + */ + private @NonNull I onServiceInited(@NonNull IBinder who) { + var service = mCastFunction.apply(who); + Objects.requireNonNull(service); + if (!mService.compareAndSet(null, service)) { + return service; + } + // Even if the service has immediately died, we should perform these tasks for consistency + for (var r : mOnStartTasks) { + mExecutor.execute(() -> r.accept(service)); + } + try { + who.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.e(mTag, "Immediate service death. Service crash-looping"); + attemptClear(who); + } + // This interface is non-null, but could represent a dead object + return service; + } +} diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 9610034caf01..e28ae952e65a 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -856,11 +856,12 @@ public class SoundDoseHelper { pw.println(); } - /*package*/void reset() { + /*package*/void reset(boolean resetISoundDose) { Log.d(TAG, "Reset the sound dose helper"); - mSoundDose.compareAndExchange(/*expectedValue=*/null, - AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + if (resetISoundDose) { + mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + } synchronized (mCsdStateLock) { try { @@ -972,7 +973,7 @@ public class SoundDoseHelper { } } - reset(); + reset(/*resetISoundDose=*/false); } private void onConfigureSafeMedia(boolean force, String caller) { diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index 712dcee55b7b..92fd9cbcf14e 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -14,10 +14,3 @@ flag { description: "This flag controls whether virtual HAL is used for testing instead of TestHal " bug: "294254230" } - -flag { - name: "mandatory_biometrics" - namespace: "biometrics_framework" - description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations" - bug: "322081563" -} 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/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 27ea1cd2f65d..d4c0b0180242 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1745,6 +1745,7 @@ public final class DisplayManagerService extends SystemService { if (projection != null) { IBinder taskWindowContainerToken = projection.getLaunchCookie() == null ? null : projection.getLaunchCookie().binder; + int taskId = projection.getTaskId(); if (taskWindowContainerToken == null) { // Record a particular display. session = ContentRecordingSession.createDisplaySession( @@ -1752,7 +1753,7 @@ public final class DisplayManagerService extends SystemService { } else { // Record a single task indicated by the launch cookie. session = ContentRecordingSession.createTaskSession( - taskWindowContainerToken); + taskWindowContainerToken, taskId); } } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java index 65c9f3556fad..0fef55da5749 100644 --- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java +++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java @@ -21,13 +21,21 @@ import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIG import android.annotation.Nullable; import android.hardware.display.DisplayManagerInternal; import android.os.Trace; +import android.util.Slog; import android.view.Display; +import com.android.server.display.utils.DebugUtils; + /** * An implementation of the offload session that keeps track of whether the session is active. * An offload session is used to control the display's brightness using the offload chip. */ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.DisplayOffloadSession { + private static final String TAG = "DisplayOffloadSessionImpl"; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.DisplayOffloadSessionImpl DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); @Nullable private final DisplayManagerInternal.DisplayOffloader mDisplayOffloader; @@ -52,6 +60,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); @@ -91,9 +107,14 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display if (mDisplayOffloader == null || mIsActive) { return false; } + Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload"); try { - return mIsActive = mDisplayOffloader.startOffload(); + mIsActive = mDisplayOffloader.startOffload(); + if (DEBUG) { + Slog.d(TAG, "startOffload = " + mIsActive); + } + return mIsActive; } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -110,6 +131,9 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display try { mDisplayOffloader.stopOffload(); mIsActive = false; + if (DEBUG) { + Slog.i(TAG, "stopOffload"); + } } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 0fcdf198eece..b97053b21b8e 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -791,10 +791,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Override public void overrideDozeScreenState(int displayState, @Display.StateReason int reason) { + Slog.i(TAG, "New offload doze override: " + Display.stateToString(displayState)); mHandler.postAtTime(() -> { if (mDisplayOffloadSession == null || !(DisplayOffloadSession.isSupportedOffloadState(displayState) - || displayState == Display.STATE_UNKNOWN)) { + || displayState == Display.STATE_UNKNOWN)) { return; } mDisplayStateController.overrideDozeScreenState(displayState, reason); @@ -1115,7 +1116,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 +1186,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(); @@ -1278,7 +1280,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void updatePowerStateInternal() { // Update the power state request. - final boolean mustNotify; + boolean mustNotify = false; final int previousPolicy; boolean mustInitialize = false; mBrightnessReasonTemp.set(null); @@ -1326,6 +1328,30 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN); } + if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) { + // Sometimes, a display-state change can come without an associated PowerRequest, + // as with DisplayOffload. For those cases, we have to make sure to also mark the + // display as "not ready" so that we can inform power-manager when the state-change is + // complete. + if (mPowerState.getScreenState() != state) { + final boolean wasReady; + synchronized (mLock) { + wasReady = mDisplayReadyLocked; + mDisplayReadyLocked = false; + mustNotify = true; + } + + if (wasReady) { + // If we went from ready to not-ready from the state-change (instead of a + // PowerRequest) there's a good chance that nothing is keeping PowerManager + // from suspending. Grab the unfinished business suspend blocker to keep the + // device awake until the display-state change goes into effect. + mWakelockController.acquireWakelock( + WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS); + } + } + } + // Animate the screen state change unless already animating. // The transition may be deferred, so after this point we will use the // actual state instead of the desired one. @@ -1334,7 +1360,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayStateController.shouldPerformScreenOffTransition()); state = mPowerState.getScreenState(); - DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController .updateBrightness(mPowerRequest, state); float brightnessState = displayBrightnessState.getBrightness(); @@ -1366,17 +1391,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 +1477,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 +3183,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 +3195,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/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 182b05a68028..44846f310348 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -168,6 +168,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { } SurfaceControl.DesiredDisplayModeSpecs modeSpecs = mSurfaceControlProxy.getDesiredDisplayModeSpecs(displayToken); + if (modeSpecs == null) { + // If mode specs is null, it most probably means that display got + // unplugged very rapidly. + Slog.w(TAG, "Desired display mode specs from SurfaceFlinger are null"); + return; + } LocalDisplayDevice device = mDevices.get(physicalDisplayId); if (device == null) { // Display was added. 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..f923cbc978ff 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 @@ -169,6 +174,11 @@ public class DisplayManagerFlags { Flags::enableSynthetic60hzModes ); + private final FlagState mOffloadDozeOverrideHoldsWakelock = new FlagState( + Flags.FLAG_OFFLOAD_DOZE_OVERRIDE_HOLDS_WAKELOCK, + Flags::offloadDozeOverrideHoldsWakelock + ); + /** * @return {@code true} if 'port' is allowed in display layout configuration file. */ @@ -327,10 +337,21 @@ 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(); } + public boolean isOffloadDozeOverrideHoldsWakelockEnabled() { + return mOffloadDozeOverrideHoldsWakelock.isEnabled(); + } + /** * @return Whether to ignore preferredRefreshRate app request or not */ @@ -373,9 +394,11 @@ public class DisplayManagerFlags { pw.println(" " + mRefactorDisplayPowerController); pw.println(" " + mResolutionBackupRestore); pw.println(" " + mUseFusionProxSensor); + pw.println(" " + mOffloadControlsDozeAutoBrightness); pw.println(" " + mPeakRefreshRatePhysicalLimit); pw.println(" " + mIgnoreAppPreferredRefreshRate); pw.println(" " + mSynthetic60hzModes); + pw.println(" " + mOffloadDozeOverrideHoldsWakelock); } private static class FlagState { 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..95d0ca381f77 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 " @@ -278,3 +289,13 @@ flag { } } +flag { + name: "offload_doze_override_holds_wakelock" + namespace: "display_manager" + description: "DisplayPowerController holds a suspend-blocker while changing the display state on behalf of offload doze override." + bug: "338403827" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} 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/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index d876a381ca7a..3c3bdd5b69f6 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -269,8 +269,7 @@ public class HdmiCecMessageValidator { addValidationInfo(Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, oneByteValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT); addValidationInfo(Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE, - new MinimumOneByteRangeValidator(0x00, 0x01), - ADDR_NOT_UNREGISTERED, ADDR_ALL); + new SingleByteRangeValidator(0x00, 0x01), ADDR_AUDIO_SYSTEM, ADDR_ALL); addValidationInfo(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, new SingleByteRangeValidator(0x00, 0x01), ADDR_NOT_UNREGISTERED, ADDR_DIRECT); 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..6e027c6d44c4 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1029,6 +1029,10 @@ public class HdmiControlService extends SystemService { /** Helper method for sending feature discovery command */ private void reportFeatures(boolean isTvDeviceSetting) { + // <Report Features> should only be sent for HDMI 2.0 + if (getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) { + return; + } // check if tv device is enabled for tv device specific RC profile setting if (isTvDeviceSetting) { if (isTvDeviceEnabled()) { @@ -1567,7 +1571,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/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java index 5646e1b9a9ef..0688fbf358ad 100644 --- a/services/core/java/com/android/server/hdmi/HdmiUtils.java +++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java @@ -175,14 +175,15 @@ final class HdmiUtils { * * @param logicalAddress the logical address to verify * @param deviceType the device type to check - * @throws IllegalArgumentException */ - static void verifyAddressType(int logicalAddress, int deviceType) { + static boolean verifyAddressType(int logicalAddress, int deviceType) { List<Integer> actualDeviceTypes = getTypeFromAddress(logicalAddress); if (!actualDeviceTypes.contains(deviceType)) { - throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType - + ", Actual:" + actualDeviceTypes); + Slog.w(TAG,"Device type mismatch:[Expected:" + deviceType + + ", Actual:" + actualDeviceTypes + "]"); + return false; } + return true; } /** diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java index 54c8c00b8889..58e146ecaa78 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java @@ -19,6 +19,7 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Base feature action class for <Request ARC Initiation>/<Request ARC Termination>. @@ -38,13 +39,14 @@ abstract class RequestArcAction extends HdmiCecFeatureAction { * @param source {@link HdmiCecLocalDevice} instance * @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type * @param callback callback to inform about the status of the action - * @throws IllegalArgumentException if device type of sourceAddress and avrAddress - * is invalid */ RequestArcAction(HdmiCecLocalDevice source, int avrAddress, IHdmiControlCallback callback) { super(source, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV) || + !HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrAddress = avrAddress; } diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index 32e274ece9ab..5ab22e1dcd61 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -47,8 +47,11 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { SetArcTransmissionStateAction(HdmiCecLocalDevice source, int avrAddress, boolean enabled) { super(source); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV) || + !HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrAddress = avrAddress; mEnabled = enabled; } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index e96963b9ae3f..f14cda1e6509 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -20,6 +20,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.util.Slog; import java.util.List; @@ -56,12 +57,14 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of sourceAddress and avrAddress is invalid */ SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(source, callback); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrLogicalAddress = avrAddress; mTargetAudioStatus = targetStatus; } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java index 99148c4ea114..08a938731dae 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java @@ -19,12 +19,14 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Feature action that handles System Audio initiated by AVR devices. */ // Seq #33 final class SystemAudioActionFromAvr extends SystemAudioAction { + private static final String TAG = "SystemAudioActionFromAvr"; /** * Constructor * @@ -32,12 +34,14 @@ final class SystemAudioActionFromAvr extends SystemAudioAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of tvAddress and avrAddress is invalid */ SystemAudioActionFromAvr(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(source, avrAddress, targetStatus, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } } @Override diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java index 5c0c272f59e0..675aa3171fbd 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java @@ -18,13 +18,14 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Feature action that handles System Audio initiated by TV devices. */ final class SystemAudioActionFromTv extends SystemAudioAction { - + private static final String TAG = "SystemAudioActionFromTv"; /** * Constructor * @@ -32,12 +33,14 @@ final class SystemAudioActionFromTv extends SystemAudioAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of tvAddress is invalid */ SystemAudioActionFromTv(HdmiCecLocalDevice sourceAddress, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(sourceAddress, avrAddress, targetStatus, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 976399d3917a..5843d72f346a 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4098,17 +4098,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (additionalSubtypeMap != newAdditionalSubtypeMap) { AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap, settings.getMethodMap()); - final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, - userId, AdditionalSubtypeMapRepository.get(userId), - DirectBootAwareness.AUTO); - InputMethodSettingsRepository.put(userId, newSettings); - if (isCurrentUser) { - final long ident = Binder.clearCallingIdentity(); - try { + final long ident = Binder.clearCallingIdentity(); + try { + final InputMethodSettings newSettings = queryInputMethodServicesInternal( + mContext, userId, AdditionalSubtypeMapRepository.get(userId), + DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(userId, newSettings); + if (isCurrentUser) { postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); - } finally { - Binder.restoreCallingIdentity(ident); } + } finally { + Binder.restoreCallingIdentity(ident); } } } @@ -4969,6 +4969,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int flags = PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS | directBootAwarenessFlags; + + // Beware that package visibility filtering will be enforced based on the effective calling + // identity (Binder.getCallingUid()), but our use case always expect Binder.getCallingUid() + // to return Process.SYSTEM_UID here. The actual filtering is implemented separately with + // canCallerAccessInputMethod(). + // TODO(b/343108534): Use PackageManagerInternal#queryIntentServices() to pass SYSTEM_UID. final List<ResolveInfo> services = userAwareContext.getPackageManager().queryIntentServices( new Intent(InputMethod.SERVICE_INTERFACE), PackageManager.ResolveInfoFlags.of(flags)); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 1c958a929546..23f947cc8452 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -367,9 +367,9 @@ final class InputMethodSubtypeSwitchingController { } protected void dump(final Printer pw, final String prefix) { - for (int i = 0; i < mUsageHistoryOfSubtypeListItemIndex.length; ++i) { - final int rank = mUsageHistoryOfSubtypeListItemIndex[i]; - final ImeSubtypeListItem item = mImeSubtypeList.get(i); + for (int rank = 0; rank < mUsageHistoryOfSubtypeListItemIndex.length; ++rank) { + final int index = mUsageHistoryOfSubtypeListItemIndex[rank]; + final ImeSubtypeListItem item = mImeSubtypeList.get(index); pw.println(prefix + "rank=" + rank + " item=" + item); } } diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java index d932bd4e6d20..b9e09605477a 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java +++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java @@ -84,9 +84,16 @@ class LocaleManagerBackupHelper { * from the delegate selector. */ private static final String LOCALES_FROM_DELEGATE_PREFS = "LocalesFromDelegatePrefs.xml"; + private static final String LOCALES_STAGED_DATA_PREFS = "LocalesStagedDataPrefs.xml"; + private static final String ARCHIVED_PACKAGES_PREFS = "ArchivedPackagesPrefs.xml"; // Stage data would be deleted on reboot since it's stored in memory. So it's retained until // retention period OR next reboot, whichever happens earlier. private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3); + // Store the locales staged data for the specified package in the SharedPreferences. The format + // is locales s:setFromDelegate + // For example: en-US s:true + private static final String STRING_SPLIT = " s:"; + private static final String KEY_STAGED_DATA_TIME = "staged_data_time"; private final LocaleManagerService mLocaleManagerService; private final PackageManager mPackageManager; @@ -94,39 +101,34 @@ class LocaleManagerBackupHelper { private final Context mContext; private final Object mStagedDataLock = new Object(); - // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using - // SparseArray because it is more memory-efficient than a HashMap. - private final SparseArray<StagedData> mStagedData; - // SharedPreferences to store packages whose app-locale was set by a delegate, as opposed to // the application setting the app-locale itself. private final SharedPreferences mDelegateAppLocalePackages; + // For unit tests + private final SparseArray<File> mStagedDataFiles; + private final File mArchivedPackagesFile; + private final BroadcastReceiver mUserMonitor; - // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving - // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data - // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the - // app is installed. - private final Set<String> mPkgsToRestore; LocaleManagerBackupHelper(LocaleManagerService localeManagerService, PackageManager packageManager, HandlerThread broadcastHandlerThread) { this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(), - new SparseArray<>(), broadcastHandlerThread, null); + broadcastHandlerThread, null, null, null); } - @VisibleForTesting LocaleManagerBackupHelper(Context context, - LocaleManagerService localeManagerService, - PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData, - HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) { + @VisibleForTesting + LocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, + PackageManager packageManager, Clock clock, HandlerThread broadcastHandlerThread, + SparseArray<File> stagedDataFiles, File archivedPackagesFile, + SharedPreferences delegateAppLocalePackages) { mContext = context; mLocaleManagerService = localeManagerService; mPackageManager = packageManager; mClock = clock; - mStagedData = stagedData; mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages - : createPersistedInfo(); - mPkgsToRestore = new ArraySet<>(); - + : createPersistedInfo(); + mArchivedPackagesFile = archivedPackagesFile; + mStagedDataFiles = stagedDataFiles; mUserMonitor = new UserMonitor(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_REMOVED); @@ -148,7 +150,7 @@ class LocaleManagerBackupHelper { } synchronized (mStagedDataLock) { - cleanStagedDataForOldEntriesLocked(); + cleanStagedDataForOldEntriesLocked(userId); } HashMap<String, LocalesInfo> pkgStates = new HashMap<>(); @@ -207,14 +209,11 @@ class LocaleManagerBackupHelper { return out.toByteArray(); } - private void cleanStagedDataForOldEntriesLocked() { - for (int i = 0; i < mStagedData.size(); i++) { - int userId = mStagedData.keyAt(i); - StagedData stagedData = mStagedData.get(userId); - if (stagedData.mCreationTimeMillis - < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) { - deleteStagedDataLocked(userId); - } + private void cleanStagedDataForOldEntriesLocked(@UserIdInt int userId) { + Long created_time = getStagedDataSp(userId).getLong(KEY_STAGED_DATA_TIME, -1); + if (created_time != -1 + && created_time < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) { + deleteStagedDataLocked(userId); } } @@ -252,20 +251,16 @@ class LocaleManagerBackupHelper { // performed simultaneously. synchronized (mStagedDataLock) { // Backups for apps which are yet to be installed. - StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>()); - for (String pkgName : pkgStates.keySet()) { LocalesInfo localesInfo = pkgStates.get(pkgName); // Check if the application is already installed for the concerned user. if (isPackageInstalledForUser(pkgName, userId)) { - if (mPkgsToRestore != null) { - mPkgsToRestore.remove(pkgName); - } + removeFromArchivedPackagesInfo(userId, pkgName); // Don't apply the restore if the locales have already been set for the app. checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId); } else { // Stage the data if the app isn't installed. - stagedData.mPackageStates.put(pkgName, localesInfo); + storeStagedDataInfo(userId, pkgName, localesInfo); if (DEBUG) { Slog.d(TAG, "Add locales=" + localesInfo.mLocales + " fromDelegate=" + localesInfo.mSetFromDelegate @@ -274,8 +269,9 @@ class LocaleManagerBackupHelper { } } - if (!stagedData.mPackageStates.isEmpty()) { - mStagedData.put(userId, stagedData); + // Create the time if the data is being staged. + if (!getStagedDataSp(userId).getAll().isEmpty()) { + storeStagedDataCreatedTime(userId); } } } @@ -293,14 +289,23 @@ class LocaleManagerBackupHelper { * added on device. */ void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) { - boolean archived = false; + int userId = UserHandle.getUserId(uid); if (extras != null) { - archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false); - if (archived && mPkgsToRestore != null) { - mPkgsToRestore.add(packageName); + // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon + // receiving the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform + // the data restoration during the second PACKAGE_ADDED broadcast, which is sent + // subsequently when the app is installed. + boolean archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false); + if (DEBUG) { + Slog.d(TAG, + "onPackageAddedWithExtras packageName: " + packageName + ", userId: " + + userId + ", archived: " + archived); + } + if (archived) { + addInArchivedPackagesInfo(userId, packageName); } } - checkStageDataAndApplyRestore(packageName, uid); + checkStageDataAndApplyRestore(packageName, userId); } /** @@ -310,9 +315,32 @@ class LocaleManagerBackupHelper { */ void onPackageUpdateFinished(String packageName, int uid) { int userId = UserHandle.getUserId(uid); - if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) { - mPkgsToRestore.remove(packageName); - checkStageDataAndApplyRestore(packageName, uid); + if (DEBUG) { + Slog.d(TAG, + "onPackageUpdateFinished userId: " + userId + ", packageName: " + packageName); + } + String user = Integer.toString(userId); + File file = getArchivedPackagesFile(); + if (file.exists()) { + SharedPreferences sp = getArchivedPackagesSp(file); + Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>())); + if (packageNames.remove(packageName)) { + SharedPreferences.Editor editor = sp.edit(); + if (packageNames.isEmpty()) { + if (!editor.remove(user).commit()) { + Slog.e(TAG, "Failed to remove the user"); + } + if (sp.getAll().isEmpty()) { + file.delete(); + } + } else { + // commit and log the result. + if (!editor.putStringSet(user, packageNames).commit()) { + Slog.e(TAG, "failed to remove the package"); + } + } + checkStageDataAndApplyRestore(packageName, userId); + } } cleanApplicationLocalesIfNeeded(packageName, userId); } @@ -347,16 +375,16 @@ class LocaleManagerBackupHelper { } } - private void checkStageDataAndApplyRestore(String packageName, int uid) { + private void checkStageDataAndApplyRestore(String packageName, int userId) { try { synchronized (mStagedDataLock) { - cleanStagedDataForOldEntriesLocked(); - - int userId = UserHandle.getUserId(uid); - if (mStagedData.contains(userId)) { - if (mPkgsToRestore != null) { - mPkgsToRestore.remove(packageName); + cleanStagedDataForOldEntriesLocked(userId); + if (!getStagedDataSp(userId).getString(packageName, "").isEmpty()) { + if (DEBUG) { + Slog.d(TAG, + "checkStageDataAndApplyRestore, remove package and restore data"); } + removeFromArchivedPackagesInfo(userId, packageName); // Perform lazy restore only if the staged data exists. doLazyRestoreLocked(packageName, userId); } @@ -417,8 +445,17 @@ class LocaleManagerBackupHelper { } } - private void deleteStagedDataLocked(@UserIdInt int userId) { - mStagedData.remove(userId); + void deleteStagedDataLocked(@UserIdInt int userId) { + File stagedFile = getStagedDataFile(userId); + SharedPreferences sp = getStagedDataSp(stagedFile); + // commit and log the result. + if (!sp.edit().clear().commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + + if (stagedFile.exists()) { + stagedFile.delete(); + } } /** @@ -434,7 +471,7 @@ class LocaleManagerBackupHelper { ATTR_PACKAGE_NAME); String languageTags = parser.getAttributeValue(/* namespace= */ null, ATTR_LOCALES); boolean delegateSelector = parser.getAttributeBoolean(/* namespace= */ null, - ATTR_DELEGATE_SELECTOR); + ATTR_DELEGATE_SELECTOR, false); if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(languageTags)) { LocalesInfo localesInfo = new LocalesInfo(languageTags, delegateSelector); @@ -473,16 +510,6 @@ class LocaleManagerBackupHelper { out.endDocument(); } - static class StagedData { - final long mCreationTimeMillis; - final HashMap<String, LocalesInfo> mPackageStates; - - StagedData(long creationTimeMillis, HashMap<String, LocalesInfo> pkgStates) { - mCreationTimeMillis = creationTimeMillis; - mPackageStates = pkgStates; - } - } - static class LocalesInfo { final String mLocales; final boolean mSetFromDelegate; @@ -508,6 +535,7 @@ class LocaleManagerBackupHelper { synchronized (mStagedDataLock) { deleteStagedDataLocked(userId); removeProfileFromPersistedInfo(userId); + removeArchivedPackagesForUser(userId); } } } catch (Exception e) { @@ -533,29 +561,162 @@ class LocaleManagerBackupHelper { return; } - StagedData stagedData = mStagedData.get(userId); - for (String pkgName : stagedData.mPackageStates.keySet()) { - LocalesInfo localesInfo = stagedData.mPackageStates.get(pkgName); + SharedPreferences sp = getStagedDataSp(userId); + String value = sp.getString(packageName, ""); + if (!value.isEmpty()) { + String[] info = value.split(STRING_SPLIT); + if (info == null || info.length != 2) { + Slog.e(TAG, "Failed to restore data"); + return; + } + LocalesInfo localesInfo = new LocalesInfo(info[0], Boolean.parseBoolean(info[1])); + checkExistingLocalesAndApplyRestore(packageName, localesInfo, userId); - if (pkgName.equals(packageName)) { + // Remove the restored entry from the staged data list. + if (!sp.edit().remove(packageName).commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + } - checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId); + // Remove the stage data entry for user if there are no more packages to restore. + if (sp.getAll().size() == 1 && sp.getLong(KEY_STAGED_DATA_TIME, -1) != -1) { + deleteStagedDataLocked(userId); + } + } - // Remove the restored entry from the staged data list. - stagedData.mPackageStates.remove(pkgName); + private File getStagedDataFile(@UserIdInt int userId) { + return mStagedDataFiles == null ? new File(Environment.getDataSystemDeDirectory(userId), + LOCALES_STAGED_DATA_PREFS) : mStagedDataFiles.get(userId); + } - // Remove the stage data entry for user if there are no more packages to restore. - if (stagedData.mPackageStates.isEmpty()) { - mStagedData.remove(userId); - } + private SharedPreferences getStagedDataSp(File file) { + return mStagedDataFiles == null ? mContext.createDeviceProtectedStorageContext() + .getSharedPreferences(file, Context.MODE_PRIVATE) + : mContext.getSharedPreferences(file, Context.MODE_PRIVATE); + } + + private SharedPreferences getStagedDataSp(@UserIdInt int userId) { + return mStagedDataFiles == null ? mContext.createDeviceProtectedStorageContext() + .getSharedPreferences(getStagedDataFile(userId), Context.MODE_PRIVATE) + : mContext.getSharedPreferences(mStagedDataFiles.get(userId), Context.MODE_PRIVATE); + } - // No need to loop further after restoring locales because the staged data will - // contain at most one entry for the newly added package. - break; + /** + * Store the staged locales info. + */ + private void storeStagedDataInfo(@UserIdInt int userId, @NonNull String packageName, + @NonNull LocalesInfo localesInfo) { + if (DEBUG) { + Slog.d(TAG, "storeStagedDataInfo, userId: " + userId + ", packageName: " + packageName + + ", localesInfo.mLocales: " + localesInfo.mLocales + + ", localesInfo.mSetFromDelegate: " + localesInfo.mSetFromDelegate); + } + String info = + localesInfo.mLocales + STRING_SPLIT + String.valueOf(localesInfo.mSetFromDelegate); + SharedPreferences sp = getStagedDataSp(userId); + // commit and log the result. + if (!sp.edit().putString(packageName, info).commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + } + + /** + * Store the time of creation for staged locales info. + */ + private void storeStagedDataCreatedTime(@UserIdInt int userId) { + SharedPreferences sp = getStagedDataSp(userId); + // commit and log the result. + if (!sp.edit().putLong(KEY_STAGED_DATA_TIME, mClock.millis()).commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + } + + private File getArchivedPackagesFile() { + return mArchivedPackagesFile == null ? new File( + Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), + ARCHIVED_PACKAGES_PREFS) : mArchivedPackagesFile; + } + + private SharedPreferences getArchivedPackagesSp(File file) { + return mArchivedPackagesFile == null ? mContext.createDeviceProtectedStorageContext() + .getSharedPreferences(file, Context.MODE_PRIVATE) + : mContext.getSharedPreferences(file, Context.MODE_PRIVATE); + } + + /** + * Add the package into the archived packages list. + */ + private void addInArchivedPackagesInfo(@UserIdInt int userId, @NonNull String packageName) { + String user = Integer.toString(userId); + SharedPreferences sp = getArchivedPackagesSp(getArchivedPackagesFile()); + Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>())); + if (DEBUG) { + Slog.d(TAG, "addInArchivedPackagesInfo before packageNames: " + packageNames + + ", packageName: " + packageName); + } + if (packageNames.add(packageName)) { + // commit and log the result. + if (!sp.edit().putStringSet(user, packageNames).commit()) { + Slog.e(TAG, "failed to add the package"); + } + } + } + + /** + * Remove the package from the archived packages list. + */ + private void removeFromArchivedPackagesInfo(@UserIdInt int userId, + @NonNull String packageName) { + File file = getArchivedPackagesFile(); + if (file.exists()) { + String user = Integer.toString(userId); + SharedPreferences sp = getArchivedPackagesSp(getArchivedPackagesFile()); + Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>())); + if (DEBUG) { + Slog.d(TAG, "removeFromArchivedPackagesInfo before packageNames: " + packageNames + + ", packageName: " + packageName); + } + if (packageNames.remove(packageName)) { + SharedPreferences.Editor editor = sp.edit(); + if (packageNames.isEmpty()) { + if (!editor.remove(user).commit()) { + Slog.e(TAG, "Failed to remove user"); + } + if (sp.getAll().isEmpty()) { + file.delete(); + } + } else { + // commit and log the result. + if (!editor.putStringSet(user, packageNames).commit()) { + Slog.e(TAG, "failed to remove the package"); + } + } } } } + /** + * Remove the user from the archived packages list. + */ + private void removeArchivedPackagesForUser(@UserIdInt int userId) { + String user = Integer.toString(userId); + File file = getArchivedPackagesFile(); + SharedPreferences sp = getArchivedPackagesSp(file); + + if (sp == null || !sp.contains(user)) { + Slog.w(TAG, "The profile is not existed in the archived package info"); + return; + } + + if (!sp.edit().remove(user).commit()) { + Slog.e(TAG, "Failed to remove user"); + } + + if (sp.getAll().isEmpty() && file.exists()) { + file.delete(); + } + } + SharedPreferences createPersistedInfo() { final File prefsFile = new File( Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), 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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index b14702dc6647..b3ab229927fe 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1243,23 +1243,24 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private void enforceFrpResolved() { + private void enforceFrpNotActive() { final int mainUserId = mInjector.getUserManagerInternal().getMainUserId(); if (mainUserId < 0) { - Slog.d(TAG, "No Main user on device; skipping enforceFrpResolved"); + Slog.d(TAG, "No Main user on device; skipping enforceFrpNotActive"); return; } - final ContentResolver cr = mContext.getContentResolver(); + final ContentResolver cr = mContext.getContentResolver(); final boolean inSetupWizard = Settings.Secure.getIntForUser(cr, Settings.Secure.USER_SETUP_COMPLETE, 0, mainUserId) == 0; - final boolean secureFrp = android.security.Flags.frpEnforcement() + final boolean isFrpActive = android.security.Flags.frpEnforcement() ? mStorage.isFactoryResetProtectionActive() - : (Settings.Global.getInt(cr, Settings.Global.SECURE_FRP_MODE, 0) == 1); + : (Settings.Global.getInt(cr, Settings.Global.SECURE_FRP_MODE, 0) == 1) + && inSetupWizard; - if (inSetupWizard && secureFrp) { - throw new SecurityException("Cannot change credential in SUW while factory reset" - + " protection is not resolved yet"); + if (isFrpActive) { + throw new SecurityException("Cannot change credential while factory reset protection" + + " is active"); } } @@ -1831,7 +1832,7 @@ public class LockSettingsService extends ILockSettings.Stub { final long identity = Binder.clearCallingIdentity(); try { - enforceFrpResolved(); + enforceFrpNotActive(); // When changing credential for profiles with unified challenge, some callers // will pass in empty credential while others will pass in the credential of // the parent user. setLockCredentialInternal() handles the formal case (empty diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 1bc2a5eb1351..363684f618cc 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRouter2; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; @@ -172,4 +173,59 @@ abstract class MediaRoute2Provider { @NonNull RoutingSessionInfo sessionInfo); void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason); } + + /** + * Holds session creation or transfer initiation information for a transfer in flight. + * + * <p>The initiator app is typically also the {@link RoutingSessionInfo#getClientPackageName() + * client app}, with the exception of the {@link MediaRouter2#getSystemController() system + * routing session} which is exceptional in that it's shared among all apps. + * + * <p>For the system routing session, the initiator app is the one that programmatically + * triggered the transfer (for example, via {@link MediaRouter2#transferTo}), or the target app + * of the proxy router that did the transfer. + * + * @see MediaRouter2.RoutingController#wasTransferInitiatedBySelf() + * @see RoutingSessionInfo#getTransferInitiatorPackageName() + * @see RoutingSessionInfo#getTransferInitiatorUserHandle() + */ + protected static class SessionCreationOrTransferRequest { + + /** + * The id of the request, or {@link + * android.media.MediaRoute2ProviderService#REQUEST_ID_NONE} if unknown. + */ + public final long mRequestId; + + /** The {@link MediaRoute2Info#getId() id} of the target route. */ + @NonNull public final String mTargetRouteId; + + @RoutingSessionInfo.TransferReason public final int mTransferReason; + + /** The {@link android.os.UserHandle} on which the initiator app is running. */ + @NonNull public final UserHandle mTransferInitiatorUserHandle; + + @NonNull public final String mTransferInitiatorPackageName; + + SessionCreationOrTransferRequest( + long requestId, + @NonNull String routeId, + @RoutingSessionInfo.TransferReason int transferReason, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { + mRequestId = requestId; + mTargetRouteId = routeId; + mTransferReason = transferReason; + mTransferInitiatorUserHandle = transferInitiatorUserHandle; + mTransferInitiatorPackageName = transferInitiatorPackageName; + } + + public boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { + return route2Info != null && mTargetRouteId.equals(route2Info.getId()); + } + + public boolean isTargetRouteIdInList(@NonNull List<String> routesList) { + return routesList.stream().anyMatch(mTargetRouteId::equals); + } + } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6ce3ab4b2d65..76930a003e46 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -79,12 +79,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { new AudioManagerBroadcastReceiver(); private final Object mRequestLock = new Object(); + @GuardedBy("mRequestLock") - private volatile SessionCreationRequest mPendingSessionCreationRequest; + private volatile SessionCreationOrTransferRequest mPendingSessionCreationOrTransferRequest; private final Object mTransferLock = new Object(); + @GuardedBy("mTransferLock") - @Nullable private volatile SessionCreationRequest mPendingTransferRequest; + @Nullable + private volatile SessionCreationOrTransferRequest mPendingTransferRequest; SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) { super(COMPONENT_NAME); @@ -180,12 +183,14 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { synchronized (mRequestLock) { // Handle the previous request as a failure if exists. - if (mPendingSessionCreationRequest != null) { - mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId, + if (mPendingSessionCreationOrTransferRequest != null) { + mCallback.onRequestFailed( + /* provider= */ this, + mPendingSessionCreationOrTransferRequest.mRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); } - mPendingSessionCreationRequest = - new SessionCreationRequest( + mPendingSessionCreationOrTransferRequest = + new SessionCreationOrTransferRequest( requestId, routeId, RoutingSessionInfo.TRANSFER_REASON_FALLBACK, @@ -247,7 +252,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { synchronized (mTransferLock) { mPendingTransferRequest = - new SessionCreationRequest( + new SessionCreationOrTransferRequest( requestId, routeId, transferReason, @@ -438,7 +443,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { boolean isTransferringToTheSelectedRoute = mPendingTransferRequest.isTargetRoute(selectedRoute); boolean canBePotentiallyTransferred = - mPendingTransferRequest.isInsideOfRoutesList(transferableRoutes); + mPendingTransferRequest.isTargetRouteIdInList(transferableRoutes); if (isTransferringToTheSelectedRoute) { transferReason = mPendingTransferRequest.mTransferReason; @@ -492,20 +497,20 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @GuardedBy("mRequestLock") private void reportPendingSessionRequestResultLockedIfNeeded( RoutingSessionInfo newSessionInfo) { - if (mPendingSessionCreationRequest == null) { + if (mPendingSessionCreationOrTransferRequest == null) { // No pending request, nothing to report. return; } - long pendingRequestId = mPendingSessionCreationRequest.mRequestId; - if (TextUtils.equals(mSelectedRouteId, mPendingSessionCreationRequest.mRouteId)) { + long pendingRequestId = mPendingSessionCreationOrTransferRequest.mRequestId; + if (mPendingSessionCreationOrTransferRequest.mTargetRouteId.equals(mSelectedRouteId)) { if (DEBUG) { Slog.w( TAG, "Session creation success to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } - mPendingSessionCreationRequest = null; + mPendingSessionCreationOrTransferRequest = null; mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo); } else { boolean isRequestedRouteConnectedBtRoute = isRequestedRouteConnectedBtRoute(); @@ -515,16 +520,16 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { Slog.w( TAG, "Session creation failed to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } - mPendingSessionCreationRequest = null; + mPendingSessionCreationOrTransferRequest = null; mCallback.onRequestFailed( this, pendingRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); } else if (DEBUG) { Slog.w( TAG, "Session creation waiting state to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } } } @@ -535,7 +540,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // where two BT routes are active so the transferable routes list is empty. // See b/307723189 for context for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) { - if (TextUtils.equals(btRoute.getId(), mPendingSessionCreationRequest.mRouteId)) { + if (TextUtils.equals( + btRoute.getId(), mPendingSessionCreationOrTransferRequest.mTargetRouteId)) { return true; } } @@ -585,51 +591,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mBluetoothRouteController.getClass().getSimpleName()); } - private static class SessionCreationRequest { - private final long mRequestId; - @NonNull private final String mRouteId; - - @RoutingSessionInfo.TransferReason private final int mTransferReason; - - @NonNull private final UserHandle mTransferInitiatorUserHandle; - @NonNull private final String mTransferInitiatorPackageName; - - SessionCreationRequest( - long requestId, - @NonNull String routeId, - @RoutingSessionInfo.TransferReason int transferReason, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { - mRequestId = requestId; - mRouteId = routeId; - mTransferReason = transferReason; - mTransferInitiatorUserHandle = transferInitiatorUserHandle; - mTransferInitiatorPackageName = transferInitiatorPackageName; - } - - private boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { - if (route2Info == null) { - return false; - } - - return isTargetRoute(route2Info.getId()); - } - - private boolean isTargetRoute(@Nullable String routeId) { - return mRouteId.equals(routeId); - } - - private boolean isInsideOfRoutesList(@NonNull List<String> routesList) { - for (String routeId : routesList) { - if (isTargetRoute(routeId)) { - return true; - } - } - - return false; - } - } - void updateVolume() { int devices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index bbb19e351b5d..e3d5c54f1d5e 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -553,7 +553,8 @@ public final class MediaProjectionManagerService extends SystemService mProjectionGrant.getLaunchCookie() == null ? null : mProjectionGrant.getLaunchCookie().binder; setReviewedConsentSessionLocked( - ContentRecordingSession.createTaskSession(taskWindowContainerToken)); + ContentRecordingSession.createTaskSession( + taskWindowContainerToken, mProjectionGrant.mTaskId)); break; } } @@ -977,6 +978,7 @@ public final class MediaProjectionManagerService extends SystemService private IBinder mToken; private IBinder.DeathRecipient mDeathEater; private boolean mRestoreSystemAlertWindow; + private int mTaskId = -1; private LaunchCookie mLaunchCookie = null; // Values for tracking token validity. @@ -1197,12 +1199,26 @@ public final class MediaProjectionManagerService extends SystemService @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION) @Override // Binder call + public void setTaskId(int taskId) { + setTaskId_enforcePermission(); + mTaskId = taskId; + } + + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION) + @Override // Binder call public LaunchCookie getLaunchCookie() { getLaunchCookie_enforcePermission(); return mLaunchCookie; } @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION) + @Override // Binder call + public int getTaskId() { + getTaskId_enforcePermission(); + return mTaskId; + } + + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION) @Override public boolean isValid() { isValid_enforcePermission(); diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 66e61c076030..3cc04570643f 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -16,6 +16,9 @@ package com.android.server.notification; +import static android.service.notification.Condition.STATE_TRUE; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; + import android.app.INotificationManager; import android.app.NotificationManager; import android.content.ComponentName; @@ -319,7 +322,20 @@ public class ConditionProviders extends ManagedServices { final Condition c = conditions[i]; final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/); r.info = info; - r.condition = c; + if (android.app.Flags.modesUi()) { + // if user turned on the mode, ignore the update unless the app also wants the + // mode on. this will update the origin of the mode and let the owner turn it + // off when the context ends + if (r.condition != null && r.condition.source == UPDATE_ORIGIN_USER) { + if (r.condition.state == STATE_TRUE && c.state == STATE_TRUE) { + r.condition = c; + } + } else { + r.condition = c; + } + } else { + r.condition = c; + } } } final int N = conditions.length; 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/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java index 9f3104cbd7b0..10169d544b73 100644 --- a/services/core/java/com/android/server/notification/NotificationShellCmd.java +++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java @@ -66,7 +66,7 @@ public class NotificationShellCmd extends ShellCommand { + " disallow_listener COMPONENT [user_id (current user if not specified)]\n" + " allow_assistant COMPONENT [user_id (current user if not specified)]\n" + " remove_assistant COMPONENT [user_id (current user if not specified)]\n" - + " set_dnd [on|none (same as on)|priority|alarms|all|off (same as all)]" + + " set_dnd [on|none (same as on)|priority|alarms|all|off (same as all)]\n" + " allow_dnd PACKAGE [user_id (current user if not specified)]\n" + " disallow_dnd PACKAGE [user_id (current user if not specified)]\n" + " reset_assistant_user_set [user_id (current user if not specified)]\n" diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index dd7603714718..f540f1db6952 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -925,10 +925,11 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } - @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + /** + * Reset the temporary services set in CTS tests, this method is primarily used to only revert + * the changes caused by CTS tests. + */ public void resetTemporaryServices() { - mContext.enforceCallingPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); synchronized (mLock) { if (mTemporaryHandler != null) { mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE); diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 92d6a826b5f5..42efd6e60fc0 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -28,6 +28,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -296,6 +297,9 @@ public abstract class InstantAppResolver { if (needsPhaseTwo) { intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE); } else { + ActivityOptions options = ActivityOptions.makeBasic() + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); // We have all of the data we need; just start the installer without a second phase if (failureIntent != null || installFailureActivity != null) { // Intent that is launched if the package couldn't be installed for any reason. @@ -322,7 +326,7 @@ public abstract class InstantAppResolver { PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE, - null /*bOptions*/, userId); + options.toBundle(), userId); IntentSender failureSender = new IntentSender(failureIntentTarget); // TODO(b/72700831): remove populating old extra intent.putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, failureSender); @@ -342,7 +346,7 @@ public abstract class InstantAppResolver { new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE, - null /*bOptions*/, userId); + options.toBundle(), userId); IntentSender successSender = new IntentSender(successIntentTarget); intent.putExtra(Intent.EXTRA_INSTANT_APP_SUCCESS, successSender); } catch (RemoteException ignore) { /* ignore; same process */ } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 57f6d2789dc5..a90473865ce5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -5153,6 +5153,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // Okay to proceed synchronized (mLock) { + assertCallerIsOwnerOrRoot(); + assertPreparedAndNotSealedLocked("setPreVerifiedDomains"); mPreVerifiedDomains = preVerifiedDomains; } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index ebdca5bcec6f..29acbcda7c4f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2308,7 +2308,7 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public @NonNull int getProfileAccessibilityLabelResId(@UserIdInt int userId) { + public @StringRes int getProfileAccessibilityLabelResId(@UserIdInt int userId) { checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getProfileAccessibilityLabelResId"); final UserInfo userInfo = getUserInfoNoChecks(userId); diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java index c6f36bfd77c9..19410e50a3fb 100644 --- a/services/core/java/com/android/server/pm/UserTypeDetails.java +++ b/services/core/java/com/android/server/pm/UserTypeDetails.java @@ -164,7 +164,7 @@ public final class UserTypeDetails { * Resource ID ({@link StringRes}) of the accessibility string that describes the user type. * This is used by accessibility services like Talkback. */ - private final @Nullable int mAccessibilityString; + private final @StringRes int mAccessibilityString; /** * The default {@link UserProperties} for the user type. @@ -183,7 +183,7 @@ public final class UserTypeDetails { @Nullable Bundle defaultSystemSettings, @Nullable Bundle defaultSecureSettings, @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters, - @Nullable int accessibilityString, + @StringRes int accessibilityString, @NonNull UserProperties defaultUserProperties) { this.mName = name; this.mEnabled = enabled; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 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/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index afcf49daf0c4..6b7f2fa1cf0d 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -54,6 +54,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.TimingsTraceLog; +import android.view.SurfaceControl; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; @@ -459,6 +460,10 @@ public final class ShutdownThread extends Thread { metricShutdownStart(); metricStarted(METRIC_SYSTEM_SERVER); + // Notify SurfaceFlinger that the device is shutting down. + // Transaction traces should be captured at this stage. + SurfaceControl.notifyShutdown(); + // Start dumping check points for this shutdown in a separate thread. Thread dumpCheckPointsThread = ShutdownCheckPoints.newDumpThread( new File(CHECK_POINTS_FILE_BASENAME)); diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index 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/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 1b6af7170756..efaa7a8598c0 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -3698,7 +3698,7 @@ public class BatteryStatsImpl extends BatteryStats { } return mTotalTimeUs + (mNesting > 0 ? (curBatteryRealtimeUs - mUpdateTimeUs) - / (mTimerPool != null ? mTimerPool.size() : 1) + / (mTimerPool != null && mTimerPool.size() > 0 ? mTimerPool.size() : 1) : 0); } 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/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java index ecfc040ae29c..9b39fa1e177c 100644 --- a/services/core/java/com/android/server/search/SearchManagerService.java +++ b/services/core/java/com/android/server/search/SearchManagerService.java @@ -61,6 +61,8 @@ public class SearchManagerService extends ISearchManager.Stub { private static final String TAG = "SearchManagerService"; final Handler mHandler; + private final MyPackageMonitor mMyPackageMonitor; + public static class Lifecycle extends SystemService { private SearchManagerService mService; @@ -95,7 +97,8 @@ public class SearchManagerService extends ISearchManager.Stub { */ public SearchManagerService(Context context) { mContext = context; - new MyPackageMonitor().register(context, null, UserHandle.ALL, true); + mMyPackageMonitor = new MyPackageMonitor(); + mMyPackageMonitor.register(context, null, UserHandle.ALL, true); new GlobalSearchProviderObserver(context.getContentResolver()); mHandler = BackgroundThread.getHandler(); } @@ -230,7 +233,6 @@ public class SearchManagerService extends ISearchManager.Stub { if (!shouldRebuildSearchableList(changingUserId)) { return; } - synchronized (mSearchables) { // Invalidate the searchable list. Searchables searchables = mSearchables.get(changingUserId); 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/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index fd4b06148c5f..0e0b78fb3206 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -466,17 +466,17 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, } /** - * @return The {@link WindowInsetsController.Appearance} flags for the top fullscreen opaque - * window in the given {@param TYPE}. + * @return The {@link WindowInsetsController.Appearance} flags for the top main app window in + * the given {@param TYPE}. */ @WindowInsetsController.Appearance private int getAppearance(TYPE source) { final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(source); - final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null - ? topFullscreenActivity.getTopFullscreenOpaqueWindow() + final WindowState topFullscreenWindow = topFullscreenActivity != null + ? topFullscreenActivity.findMainWindow() : null; - if (topFullscreenOpaqueWindow != null) { - return topFullscreenOpaqueWindow.mAttrs.insetsFlags.appearance; + if (topFullscreenWindow != null) { + return topFullscreenWindow.mAttrs.insetsFlags.appearance; } return 0; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index af5956786c0a..004884925f02 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; } @@ -7658,20 +7660,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - /** - * @return The to top most child window for which {@link LayoutParams#isFullscreen()} returns - * true and isn't fully transparent. - */ - WindowState getTopFullscreenOpaqueWindow() { - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState win = mChildren.get(i); - if (win != null && win.mAttrs.isFullscreen() && !win.isFullyTransparent()) { - return win; - } - } - return null; - } - WindowState findMainWindow() { return findMainWindow(true); } @@ -8560,6 +8548,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 (Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio() + && !mLetterboxUiController.hasFullscreenOverride()) { + resolveAspectRatioRestriction(newParentConfiguration); + } final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets(); if (compatDisplayInsets != null) { resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets); @@ -8572,13 +8567,13 @@ 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()) { + if (!Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio() + && !mInSizeCompatModeForBounds && !mLetterboxUiController.hasFullscreenOverride()) { resolveAspectRatioRestriction(newParentConfiguration); } @@ -8801,7 +8796,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 +8825,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 +8865,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 +8886,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 +8910,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + boolean isImmersiveMode(@NonNull Rect parentBounds) { + if (!Flags.immersiveAppRepositioning()) { + return false; + } + if (!mResolveConfigHint.mUseOverrideInsetsForConfig + && mWmService.mFlags.mInsetsDecoupledConfiguration) { + 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 +8968,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mLetterboxBoundsForFixedOrientationAndAspectRatio != null; } + boolean isLetterboxedForAspectRatioOnly() { + return mLetterboxBoundsForAspectRatio != null; + } + boolean isAspectRatioApplied() { return mIsAspectRatioApplied; } @@ -9235,11 +9264,13 @@ 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 boolean useResolvedBounds = Flags.immersiveAppRepositioning() + ? isAspectRatioApplied() : isLetterboxedForFixedOrientationAndAspectRatio(); + final Rect containerBounds = useResolvedBounds ? new Rect(resolvedBounds) : newParentConfiguration.windowConfiguration.getBounds(); - final Rect containerAppBounds = isLetterboxedForFixedOrientationAndAspectRatio() - ? new Rect(getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()) + final Rect containerAppBounds = useResolvedBounds + ? 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/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 0e4f0335118d..f91ef1d41a0c 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -1099,10 +1099,6 @@ class BackNavigationController { } void finishPresentAnimations() { - if (!mComposed) { - return; - } - if (mCloseAdaptor != null) { mCloseAdaptor.mTarget.cancelAnimation(); mCloseAdaptor = null; @@ -1131,8 +1127,10 @@ class BackNavigationController { } void clearBackAnimateTarget() { - finishPresentAnimations(); - mComposed = false; + if (mComposed) { + mComposed = false; + finishPresentAnimations(); + } mWaitTransition = false; mStartingSurfaceTargetMatch = false; mSwitchType = UNKNOWN; @@ -1270,6 +1268,8 @@ class BackNavigationController { .setContainerLayer() .setHidden(false) .setParent(task.getSurfaceControl()) + .setCallsite( + "BackWindowAnimationAdaptorWrapper.getOrCreateAnimationTarget") .build(); mCloseTransaction = new SurfaceControl.Transaction(); mCloseTransaction.reparent(leashSurface, null); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 207707efb51d..ac2c886d1b66 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static android.os.Process.INVALID_PID; import static android.os.Process.INVALID_UID; +import static android.os.Process.ROOT_UID; import static android.os.Process.SYSTEM_UID; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; @@ -385,6 +386,10 @@ public class BackgroundActivityStartController { return BackgroundStartPrivileges.NONE; case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: // no explicit choice by the app - let us decide what to do + if (callingUid == ROOT_UID || callingUid == SYSTEM_UID) { + // root and system must always opt in explicitly + return BackgroundStartPrivileges.NONE; + } if (callingPackage != null) { // determine based on the calling/creating package boolean changeEnabled = CompatChanges.isChangeEnabled( diff --git a/services/core/java/com/android/server/wm/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/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 22ca82a29d59..1e7de2be87fa 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -286,6 +286,7 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal .setName(name) .setCallsite("createSurfaceForGestureMonitor") .setParent(inputOverlay) + .setCallsite("InputManagerCallback.createSurfaceForGestureMonitor") .build(); } } 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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 72f592b577da..e07b72a05123 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -33,6 +33,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.Process.SYSTEM_UID; import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE; +import static android.view.WindowInsets.Type.mandatorySystemGestures; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; @@ -60,6 +61,8 @@ import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Insets; +import android.graphics.Rect; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; @@ -71,7 +74,9 @@ import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.InsetsState; import android.view.MotionEvent; +import android.view.WindowInsets; import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.internal.annotations.VisibleForTesting; @@ -208,6 +213,7 @@ class RecentTasks { private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>(); private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>(); private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray(); + private final Rect mTmpRect = new Rect(); // TODO(b/127498985): This is currently a rough heuristic for interaction inside an app private final PointerEventListener mListener = new PointerEventListener() { @@ -229,12 +235,27 @@ class RecentTasks { if (win == null) { return; } + + // Verify the touch is within the mandatory system gesture inset bounds of the + // window, use the raw insets state to ignore window z-order + final InsetsState insetsState = dc.getInsetsStateController() + .getRawInsetsState(); + mTmpRect.set(win.getFrame()); + mTmpRect.inset(insetsState.calculateInsets(win.getFrame(), + mandatorySystemGestures(), false /* ignoreVisibility */)); + if (!mTmpRect.contains(x, y)) { + return; + } + // Unfreeze the task list once we touch down in a task final boolean isAppWindowTouch = FIRST_APPLICATION_WINDOW <= win.mAttrs.type && win.mAttrs.type <= LAST_APPLICATION_WINDOW; if (isAppWindowTouch) { final Task stack = mService.getTopDisplayFocusedRootTask(); final Task topTask = stack != null ? stack.getTopMostTask() : null; + ProtoLog.i(WM_DEBUG_TASKS, "Resetting frozen recents task list" + + " reason=app touch win=%s x=%d y=%d insetFrame=%s", win, x, y, + mTmpRect); resetFreezeTaskListReordering(topTask); } } @@ -301,6 +322,8 @@ class RecentTasks { mFreezeTaskListReordering = true; } + ProtoLog.i(WM_DEBUG_TASKS, "Setting frozen recents task list"); + // Always update the reordering time when this is called to ensure that the timeout // is reset mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable); @@ -344,6 +367,7 @@ class RecentTasks { final Task focusedStack = mService.getTopDisplayFocusedRootTask(); final Task topTask = focusedStack != null ? focusedStack.getTopMostTask() : null; final Task reorderToEndTask = topTask != null && topTask.hasChild() ? topTask : null; + ProtoLog.i(WM_DEBUG_TASKS, "Resetting frozen recents task list reason=timeout"); resetFreezeTaskListReordering(reorderToEndTask); } } diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 05eeeb381d8a..cff40c768381 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -140,23 +140,15 @@ public class StartingSurfaceController { } StartingSurface createTaskSnapshotSurface(ActivityRecord activity, TaskSnapshot taskSnapshot) { - final WindowState topFullscreenOpaqueWindow; final Task task = activity.getTask(); if (task == null) { Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find task for activity=" + activity); return null; } - final ActivityRecord topFullscreenActivity = task.getTopFullscreenActivity(); - if (topFullscreenActivity == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find top fullscreen for task=" - + task); - return null; - } - topFullscreenOpaqueWindow = topFullscreenActivity.getTopFullscreenOpaqueWindow(); - if (topFullscreenOpaqueWindow == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: no opaque window in " - + topFullscreenActivity); + final WindowState mainWindow = activity.findMainWindow(false); + if (mainWindow == null) { + Slog.w(TAG, "TaskSnapshotSurface.create: no main window in " + activity); return null; } if (activity.mDisplayContent.getRotation() != taskSnapshot.getRotation()) { 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/Task.java b/services/core/java/com/android/server/wm/Task.java index a555388ab233..6c48e9586fd9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1931,6 +1931,9 @@ class Task extends TaskFragment { if (td.getSystemBarsAppearance() == 0) { td.setSystemBarsAppearance(atd.getSystemBarsAppearance()); } + if (td.getTopOpaqueSystemBarsAppearance() == 0 && r.fillsParent()) { + td.setTopOpaqueSystemBarsAppearance(atd.getSystemBarsAppearance()); + } if (td.getNavigationBarColor() == 0) { td.setNavigationBarColor(atd.getNavigationBarColor()); td.setEnsureNavigationBarContrastWhenTransparent( @@ -3624,14 +3627,15 @@ class Task extends TaskFragment { // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. info.taskInfo.configuration.setTo(activity.getConfiguration()); - final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(); - if (topFullscreenActivity != null) { - final WindowState topFullscreenOpaqueWindow = - topFullscreenActivity.getTopFullscreenOpaqueWindow(); - if (topFullscreenOpaqueWindow != null) { - info.topOpaqueWindowInsetsState = - topFullscreenOpaqueWindow.getInsetsStateWithVisibilityOverride(); - info.topOpaqueWindowLayoutParams = topFullscreenOpaqueWindow.getAttrs(); + if (!Flags.drawSnapshotAspectRatioMatch()) { + final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(); + if (topFullscreenActivity != null) { + final WindowState mainWindow = topFullscreenActivity.findMainWindow(false); + if (mainWindow != null) { + info.topOpaqueWindowInsetsState = + mainWindow.getInsetsStateWithVisibilityOverride(); + info.topOpaqueWindowLayoutParams = mainWindow.getAttrs(); + } } } return info; @@ -6879,7 +6883,7 @@ class Task extends TaskFragment { private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) { t.setLayer(mContainerSurface, layer); - t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible()); + t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible() || mIsBoosted); for (int i = 0; i < mPendingClientTransactions.size(); i++) { t.merge(mPendingClientTransactions.get(i)); } 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/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 7ec31d5a8ecb..c972eee84ea3 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -236,6 +236,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { @VisibleForTesting ArrayList<Runnable> mTransactionCompletedListeners = null; + private ArrayList<Runnable> mTransitionEndedListeners = null; + /** Custom activity-level animation options and callbacks. */ private TransitionInfo.AnimationOptions mOverrideOptions; private IRemoteCallback mClientAnimationStartCallback = null; @@ -1473,6 +1475,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mSnapshotController.onTransitionFinish(mType, mTargets); // Resume snapshot persist thread after snapshot controller analysis this transition. mController.updateAnimatingState(); + + invokeTransitionEndedListeners(); + } + + private void invokeTransitionEndedListeners() { + if (mTransitionEndedListeners == null) { + return; + } + for (int i = 0; i < mTransitionEndedListeners.size(); i++) { + mTransitionEndedListeners.get(i).run(); + } + mTransitionEndedListeners = null; } private void commitConfigAtEndActivities() { @@ -1584,6 +1598,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Syncengine abort will call through to onTransactionReady() mSyncEngine.abort(mSyncId); mController.dispatchLegacyAppTransitionCancelled(); + invokeTransitionEndedListeners(); } /** Immediately moves this to playing even if it isn't started yet. */ @@ -1902,6 +1917,20 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } /** + * Adds a listener that will be executed after the transition is finished or aborted. + */ + void addTransitionEndedListener(Runnable listener) { + if (mState != STATE_COLLECTING && mState != STATE_STARTED) { + throw new IllegalStateException( + "Can't register listeners if the transition isn't collecting. state=" + mState); + } + if (mTransitionEndedListeners == null) { + mTransitionEndedListeners = new ArrayList<>(); + } + mTransitionEndedListeners.add(listener); + } + + /** * Checks if the transition contains order changes. * * This is a shallow check that doesn't account for collection in parallel, unlike @@ -2639,7 +2668,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( - "Transition Root: " + leashReference.getName()).build(); + "Transition Root: " + leashReference.getName()) + .setCallsite("Transition.calculateTransitionRoots").build(); rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots"); // Update layers to start transaction because we prevent assignment during collect, so // the layer of transition root can be correct. @@ -3757,7 +3787,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) { // This isn't cheap, so only do it for rotation change. changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma( - buffer, screenshotBuffer.getColorSpace()); + buffer, screenshotBuffer.getColorSpace(), wc.mSurfaceControl); } SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 2dc439da992d..08123232866b 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -113,10 +113,9 @@ class TransitionController { private static final int LEGACY_STATE_READY = 1; private static final int LEGACY_STATE_RUNNING = 2; - private ITransitionPlayer mTransitionPlayer; + private final ArrayList<TransitionPlayerRecord> mTransitionPlayers = new ArrayList<>(); final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter(); - private WindowProcessController mTransitionPlayerProc; final ActivityTaskManagerService mAtm; BLASTSyncEngine mSyncEngine; @@ -175,8 +174,6 @@ class TransitionController { final Lock mRunningLock = new Lock(); - private final IBinder.DeathRecipient mTransitionPlayerDeath; - static class QueuedTransition { final Transition mTransition; final OnStartCollect mOnStartCollect; @@ -243,11 +240,6 @@ class TransitionController { TransitionController(ActivityTaskManagerService atm) { mAtm = atm; mRemotePlayer = new RemotePlayer(atm); - mTransitionPlayerDeath = () -> { - synchronized (mAtm.mGlobalLock) { - detachPlayer(); - } - }; } void setWindowManager(WindowManagerService wms) { @@ -266,11 +258,10 @@ class TransitionController { mSyncEngine.addOnIdleListener(this::tryStartCollectFromQueue); } - @VisibleForTesting - void detachPlayer() { - if (mTransitionPlayer == null) return; - // Immediately set to null so that nothing inadvertently starts/queues. - mTransitionPlayer = null; + void flushRunningTransitions() { + // Temporarily clear so that nothing gets started/queued while flushing + final ArrayList<TransitionPlayerRecord> temp = new ArrayList<>(mTransitionPlayers); + mTransitionPlayers.clear(); // Clean-up/finish any playing transitions. Backwards since they can remove themselves. for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { mPlayingTransitions.get(i).cleanUpOnFailure(); @@ -285,9 +276,10 @@ class TransitionController { if (mCollectingTransition != null) { mCollectingTransition.abort(); } - mTransitionPlayerProc = null; mRemotePlayer.clear(); mRunningLock.doNotifyLocked(); + // Restore the rest of the player stack + mTransitionPlayers.addAll(temp); } /** @see #createTransition(int, int) */ @@ -302,7 +294,7 @@ class TransitionController { @NonNull Transition createTransition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags) { - if (mTransitionPlayer == null) { + if (mTransitionPlayers.isEmpty()) { throw new IllegalStateException("Shell Transitions not enabled"); } if (mCollectingTransition != null) { @@ -321,7 +313,7 @@ class TransitionController { if (mCollectingTransition != null) { throw new IllegalStateException("Simultaneous transition collection not supported."); } - if (mTransitionPlayer == null) { + if (mTransitionPlayers.isEmpty()) { // If sysui has been killed (by a test) or crashed, we can temporarily have no player // In this case, abort the transition. transition.abort(); @@ -339,30 +331,56 @@ class TransitionController { void registerTransitionPlayer(@Nullable ITransitionPlayer player, @Nullable WindowProcessController playerProc) { - try { - // Note: asBinder() can be null if player is same process (likely in a test). - if (mTransitionPlayer != null) { - if (mTransitionPlayer.asBinder() != null) { - mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0); - } - detachPlayer(); - } - if (player.asBinder() != null) { - player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); - } - mTransitionPlayer = player; - mTransitionPlayerProc = playerProc; - } catch (RemoteException e) { - throw new RuntimeException("Unable to set transition player"); + if (!mTransitionPlayers.isEmpty()) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Registering transition " + + "player %s over %d other players", player.asBinder(), + mTransitionPlayers.size()); + // flush currently running transitions so that the new player doesn't get + // intermediate state + flushRunningTransitions(); + } else { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Registering transition " + + "player %s ", player.asBinder()); } + mTransitionPlayers.add(new TransitionPlayerRecord(player, playerProc)); + } + + @VisibleForTesting + void unregisterTransitionPlayer(@NonNull ITransitionPlayer player) { + int idx = mTransitionPlayers.size() - 1; + for (; idx >= 0; --idx) { + if (mTransitionPlayers.get(idx).mPlayer.asBinder() == player.asBinder()) break; + } + if (idx < 0) { + ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Attempt to unregister " + + "transition player %s but it isn't registered", player.asBinder()); + return; + } + final boolean needsFlush = idx == (mTransitionPlayers.size() - 1); + final TransitionPlayerRecord record = mTransitionPlayers.remove(idx); + if (needsFlush) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Unregistering active " + + "transition player %s at index=%d leaving %d in stack", player.asBinder(), + idx, mTransitionPlayers.size()); + } else { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Unregistering transition " + + "player %s at index=%d leaving %d in stack", player.asBinder(), idx, + mTransitionPlayers.size()); + } + record.unlinkToDeath(); + if (!needsFlush) { + // Not the active player, so no need to flush transitions. + return; + } + flushRunningTransitions(); } @Nullable ITransitionPlayer getTransitionPlayer() { - return mTransitionPlayer; + return mTransitionPlayers.isEmpty() ? null : mTransitionPlayers.getLast().mPlayer; } boolean isShellTransitionsEnabled() { - return mTransitionPlayer != null; + return !mTransitionPlayers.isEmpty(); } /** @return {@code true} if using shell-transitions rotation instead of fixed-rotation. */ @@ -677,7 +695,7 @@ class TransitionController { Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, @NonNull WindowContainer readyGroupRef) { - if (mTransitionPlayer == null) { + if (mTransitionPlayers.isEmpty()) { return null; } Transition newTransition = null; @@ -728,7 +746,7 @@ class TransitionController { transition.getToken(), null)); return transition; } - if (mTransitionPlayer == null || transition.isAborted()) { + if (mTransitionPlayers.isEmpty() || transition.isAborted()) { // Apparently, some tests will kill(and restart) systemui, so there is a chance that // the player might be transiently null. if (transition.isCollecting()) { @@ -757,7 +775,8 @@ class TransitionController { transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos(); transition.mLogger.mRequest = request; - mTransitionPlayer.requestStartTransition(transition.getToken(), request); + mTransitionPlayers.getLast().mPlayer.requestStartTransition( + transition.getToken(), request); if (remoteTransition != null) { transition.setRemoteAnimationApp(remoteTransition.getAppThread()); } @@ -773,7 +792,7 @@ class TransitionController { * @return the new transition if it was created for this request, `null` otherwise. */ Transition requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) { - if (mTransitionPlayer == null || isCollecting()) return null; + if (mTransitionPlayers.isEmpty() || isCollecting()) return null; if (!wc.isVisibleRequested()) return null; return requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), wc.asTask(), null /* remoteTransition */, null /* displayChange */); @@ -1250,11 +1269,13 @@ class TransitionController { /** Updates the process state of animation player. */ private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) { - if (mTransitionPlayerProc == null) return; + if (mTransitionPlayers.isEmpty()) return; + final TransitionPlayerRecord record = mTransitionPlayers.getLast(); + if (record.mPlayerProc == null) return; if (isPlaying) { - mTransitionPlayerProc.setRunningRemoteAnimation(true); + record.mPlayerProc.setRunningRemoteAnimation(true); } else if (mPlayingTransitions.isEmpty()) { - mTransitionPlayerProc.setRunningRemoteAnimation(false); + record.mPlayerProc.setRunningRemoteAnimation(false); mRemotePlayer.clear(); } } @@ -1416,7 +1437,7 @@ class TransitionController { */ @NonNull Transition createAndStartCollecting(int type) { - if (mTransitionPlayer == null) { + if (mTransitionPlayers.isEmpty()) { return null; } if (!mQueuedTransitions.isEmpty()) { @@ -1477,6 +1498,40 @@ class TransitionController { void onCollectStarted(boolean deferred); } + private class TransitionPlayerRecord { + final ITransitionPlayer mPlayer; + IBinder.DeathRecipient mDeath = null; + private WindowProcessController mPlayerProc; + + TransitionPlayerRecord(@NonNull ITransitionPlayer player, + @Nullable WindowProcessController playerProc) { + mPlayer = player; + mPlayerProc = playerProc; + try { + linkToDeath(); + } catch (RemoteException e) { + throw new RuntimeException("Unable to set transition player"); + } + } + + private void linkToDeath() throws RemoteException { + // Note: asBinder() can be null if player is same process (likely in a test). + if (mPlayer.asBinder() == null) return; + mDeath = () -> { + synchronized (mAtm.mGlobalLock) { + unregisterTransitionPlayer(mPlayer); + } + }; + mPlayer.asBinder().linkToDeath(mDeath, 0); + } + + void unlinkToDeath() { + if (mPlayer.asBinder() == null || mDeath == null) return; + mPlayer.asBinder().unlinkToDeath(mDeath, 0); + mDeath = null; + } + } + /** * This manages the animating state of processes that are running remote animations for * {@link #mTransitionPlayerProc}. diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java index debe7946dc95..9b868bebd868 100644 --- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java +++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java @@ -54,6 +54,7 @@ class TrustedOverlayHost { final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null) .setContainerLayer() .setHidden(true) + .setCallsite("TrustedOverlayHost.requireOverlaySurfaceControl") .setName("Overlay Host Leash"); mSurfaceControl = b.build(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b6035519fdba..94a22394cf41 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1525,7 +1525,7 @@ public class WindowManagerService extends IWindowManager.Stub InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame, float[] outSizeCompatScale) { - outActiveControls.set(null); + outActiveControls.set(null, false /* copyControls */); int[] appOp = new int[1]; final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; @@ -2317,7 +2317,7 @@ public class WindowManagerService extends IWindowManager.Stub InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, Bundle outBundle, WindowRelayoutResult outRelayoutResult) { if (outActiveControls != null) { - outActiveControls.set(null); + outActiveControls.set(null, false /* copyControls */); } int result = 0; boolean configChanged = false; @@ -2745,23 +2745,14 @@ public class WindowManagerService extends IWindowManager.Stub private void getInsetsSourceControls(WindowState win, InsetsSourceControl.Array outArray) { final InsetsSourceControl[] controls = win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win); - if (controls != null) { - final int length = controls.length; - final InsetsSourceControl[] outControls = new InsetsSourceControl[length]; - for (int i = 0; i < length; i++) { - // We will leave the critical section before returning the leash to the client, - // so we need to copy the leash to prevent others release the one that we are - // about to return. - if (controls[i] != null) { - // This source control is an extra copy if the client is not local. By setting - // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of - // SurfaceControl.writeToParcel. - outControls[i] = new InsetsSourceControl(controls[i]); - outControls[i].setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE); - } - } - outArray.set(outControls); - } + // We will leave the critical section before returning the leash to the client, + // so we need to copy the leash to prevent others release the one that we are + // about to return. + outArray.set(controls, true /* copyControls */); + // This source control is an extra copy if the client is not local. By setting + // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of + // SurfaceControl.writeToParcel. + outArray.setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE); } private void tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator) { @@ -8619,8 +8610,8 @@ public class WindowManagerService extends IWindowManager.Stub return true; } // For a task session, find the activity identified by the launch cookie. - final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie( - incomingSession.getTokenToRecord()); + final WindowContainerInfo wci = + getTaskWindowContainerInfoForRecordingSession(incomingSession); if (wci == null) { Slog.w(TAG, "Handling a new recording session; unable to find the " + "WindowContainerToken"); @@ -9082,35 +9073,58 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with - * the given launch cookie. + * Retrieve the {@link WindowContainerInfo} of the task that was launched for MediaProjection. * - * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an - * activity + * @param session the {@link ContentRecordingSession} containing the launch cookie and/or + * task id of the Task started for capture. * @return a token representing the task containing the activity started with the given launch * cookie, or {@code null} if the token couldn't be found. */ @VisibleForTesting @Nullable - WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) { - // Find the activity identified by the launch cookie. - final ActivityRecord targetActivity = - mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie); - if (targetActivity == null) { - Slog.w(TAG, "Unable to find the activity for this launch cookie"); - return null; + WindowContainerInfo getTaskWindowContainerInfoForRecordingSession( + @NonNull ContentRecordingSession session) { + WindowContainerToken taskWindowContainerToken = null; + ActivityRecord targetActivity = null; + Task targetTask = null; + + // First attempt to find the launched task by looking for the launched activity with the + // matching launch cookie. + if (session.getTokenToRecord() != null) { + IBinder launchCookie = session.getTokenToRecord(); + targetActivity = mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie); + if (targetActivity == null) { + Slog.w(TAG, "Unable to find the activity for this launch cookie"); + } else { + if (targetActivity.getTask() == null) { + Slog.w(TAG, "Unable to find the task for this launch cookie"); + } else { + targetTask = targetActivity.getTask(); + taskWindowContainerToken = targetTask.mRemoteToken.toWindowContainerToken(); + } + } } - if (targetActivity.getTask() == null) { - Slog.w(TAG, "Unable to find the task for this launch cookie"); - return null; + + // In the case we can't find an activity with a matching launch cookie, it could be due to + // the launched activity being closed, but the launched task is still open, so now attempt + // to look for the task directly. + if (taskWindowContainerToken == null && session.getTaskId() != -1) { + int targetTaskId = session.getTaskId(); + targetTask = mRoot.getTask(task -> task.isTaskId(targetTaskId)); + if (targetTask == null) { + Slog.w(TAG, "Unable to find the task for this projection"); + } else { + taskWindowContainerToken = targetTask.mRemoteToken.toWindowContainerToken(); + } } - WindowContainerToken taskWindowContainerToken = - targetActivity.getTask().mRemoteToken.toWindowContainerToken(); + + // If we were unable to find the launched task in either fashion, then something must have + // wrong (i.e. the task was closed before capture started). if (taskWindowContainerToken == null) { - Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName()); + Slog.w(TAG, "Unable to find the WindowContainerToken for ContentRecordingSession"); return null; } - return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken); + return new WindowContainerInfo(targetTask.effectiveUid, taskWindowContainerToken); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 99c47360418b..6221d9656962 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1613,6 +1613,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: { if (Flags.activityEmbeddingInteractiveDividerFlag()) { + final Task task = taskFragment.getTask(); + if (task == null) { + break; + } final SurfaceControl.Transaction clientTransaction = operation.getSurfaceTransaction(); if (clientTransaction != null) { @@ -1621,10 +1625,22 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // any invalid operations. clientTransaction.sanitize(caller.mPid, caller.mUid); } - taskFragment.getTask().setDecorSurfaceBoosted( - taskFragment, - operation.getBooleanValue() /* isBoosted */, - clientTransaction); + + if (transition != null) { + // The decor surface boost/unboost must happen after the transition is + // completed. Otherwise, the decor surface could be moved before Shell + // completes the transition, causing flicker. + transition.addTransitionEndedListener(() -> + task.setDecorSurfaceBoosted( + taskFragment, + operation.getBooleanValue() /* isBoosted */, + clientTransaction)); + } else { + task.setDecorSurfaceBoosted( + taskFragment, + operation.getBooleanValue() /* isBoosted */, + clientTransaction); + } } break; } @@ -2088,6 +2104,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } @Override + public void unregisterTransitionPlayer(ITransitionPlayer player) { + enforceTaskPermission("unregisterTransitionPlayer()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + mTransitionController.unregisterTransitionPlayer(player); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public ITransitionMetricsReporter getTransitionMetricsReporter() { return mTransitionController.mTransitionMetricsReporter; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 90c287c056e8..d7c49ac81a6c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -188,6 +188,7 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyCache; +import android.app.servertransaction.WindowStateInsetsControlChangeItem; import android.app.servertransaction.WindowStateResizeItem; import android.content.Context; import android.content.res.Configuration; @@ -3818,11 +3819,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final InsetsStateController stateController = getDisplayContent().getInsetsStateController(); - mLastReportedActiveControls.set(stateController.getControlsForDispatch(this)); - try { - mClient.insetsControlChanged(getCompatInsetsState(), mLastReportedActiveControls); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e); + final InsetsState insetsState = getCompatInsetsState(); + mLastReportedActiveControls.set(stateController.getControlsForDispatch(this), + false /* copyControls */); + if (Flags.insetsControlChangedItem()) { + getProcess().scheduleClientTransactionItem(WindowStateInsetsControlChangeItem.obtain( + mClient, insetsState, mLastReportedActiveControls)); + } else { + try { + mClient.insetsControlChanged(insetsState, mLastReportedActiveControls); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e); + } } } diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp index ddbc5354358c..e42f7f8be0ca 100644 --- a/services/core/jni/BroadcastRadio/convert.cpp +++ b/services/core/jni/BroadcastRadio/convert.cpp @@ -433,7 +433,7 @@ static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfi gjni.AmBandDescriptor.clazz, gjni.AmBandDescriptor.cstor, region, config.type, config.lowerLimit, config.upperLimit, spacing, am.stereo)); } else { - ALOGE("Unsupported band type: %d", config.type); + ALOGE("Unsupported band type: %d", static_cast<int>(config.type)); return nullptr; } } @@ -451,7 +451,7 @@ JavaRef<jobject> BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, return make_javaref(env, env->NewObject( gjni.AmBandConfig.clazz, gjni.AmBandConfig.cstor, descriptor.get())); } else { - ALOGE("Unsupported band type: %d", config.type); + ALOGE("Unsupported band type: %d", static_cast<int>(config.type)); return nullptr; } } @@ -539,9 +539,9 @@ JavaRef<jobject> MetadataFromHal(JNIEnv *env, const hidl_vec<V1_0::MetaData> &me item.clockValue.timezoneOffsetInMinutes); break; default: - ALOGW("invalid metadata type %d", item.type); + ALOGW("invalid metadata type %d", static_cast<int>(item.type)); } - ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, item.type); + ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, static_cast<int>(item.type)); } return jMetadata; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index d733762e90e5..669a999c921e 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(); } /** @@ -1553,7 +1588,7 @@ final class DevicePolicyEngine { private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) { synchronized (mLock) { return mEnforcingAdmins.contains(userId) - ? mEnforcingAdmins.get(userId) : Collections.emptySet(); + ? new HashSet<>(mEnforcingAdmins.get(userId)) : Collections.emptySet(); } } @@ -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/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index c16c61271280..cc340c0a5f79 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -287,6 +287,7 @@ public class MidiService extends IMidiManager.Stub { } public void deviceAdded(Device device) { + Log.d(TAG, "deviceAdded() " + device.getUserId() + " userId:" + getUserId()); // ignore devices that our client cannot access if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return; @@ -301,6 +302,7 @@ public class MidiService extends IMidiManager.Stub { } public void deviceRemoved(Device device) { + Log.d(TAG, "deviceRemoved() " + device.getUserId() + " userId:" + getUserId()); // ignore devices that our client cannot access if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return; @@ -315,6 +317,7 @@ public class MidiService extends IMidiManager.Stub { } public void deviceStatusChanged(Device device, MidiDeviceStatus status) { + Log.d(TAG, "deviceStatusChanged() " + device.getUserId() + " userId:" + getUserId()); // ignore devices that our client cannot access if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return; @@ -1303,7 +1306,7 @@ public class MidiService extends IMidiManager.Stub { String[] inputPortNames, String[] outputPortNames, Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo, boolean isPrivate, int uid, int defaultProtocol, int userId) { - Log.d(TAG, "addDeviceLocked()" + uid + " type:" + type); + Log.d(TAG, "addDeviceLocked() " + uid + " type:" + type + " userId:" + userId); // Limit the number of devices per app. int deviceCountForApp = 0; diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 488fe57cf6f8..9f9764853bef 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -370,18 +370,18 @@ public final class ProfcollectForwardingService extends SystemService { } private static void createAndUploadReport(ProfcollectForwardingService pfs) { - String reportName; - try { - reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip"; - } catch (RemoteException e) { - Log.e(LOG_TAG, "Failed to create report: " + e.getMessage()); - return; - } - if (!pfs.mUploadEnabled) { - Log.i(LOG_TAG, "Upload is not enabled."); - return; - } BackgroundThread.get().getThreadHandler().post(() -> { + String reportName; + try { + reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip"; + } catch (RemoteException e) { + Log.e(LOG_TAG, "Failed to create report: " + e.getMessage()); + return; + } + if (!pfs.mUploadEnabled) { + Log.i(LOG_TAG, "Upload is not enabled."); + return; + } Intent intent = new Intent() .setPackage("com.android.shell") .setAction("com.android.shell.action.PROFCOLLECT_UPLOAD") diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp index a70802ad3337..6c4158e60ebb 100644 --- a/services/robotests/Android.bp +++ b/services/robotests/Android.bp @@ -64,6 +64,8 @@ android_robolectric_test { instrumentation_for: "FrameworksServicesLib", upstream: true, + + strict_mode: false, } filegroup { diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp index 569786b1e26f..3ace3fb11506 100644 --- a/services/robotests/backup/Android.bp +++ b/services/robotests/backup/Android.bp @@ -68,4 +68,6 @@ android_robolectric_test { upstream: true, + strict_mode: false, + } 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..8fd1e6baf522 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) ); } @@ -1477,6 +1538,8 @@ public final class DisplayPowerControllerTest { @Test public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() { + when(mDisplayManagerFlagsMock.isOffloadDozeOverrideHoldsWakelockEnabled()).thenReturn(true); + // set up. int initState = Display.STATE_DOZE; int supportedTargetState = Display.STATE_DOZE_SUSPEND; @@ -1495,10 +1558,15 @@ public final class DisplayPowerControllerTest { mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState + reset(mHolder.wakelockController); mHolder.dpc.overrideDozeScreenState( supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY); advanceTime(1); // Run updatePowerState + // Should get a wakelock to notify powermanager + verify(mHolder.wakelockController, atLeastOnce()).acquireWakelock( + eq(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS)); + verify(mHolder.displayPowerState) .setScreenState(supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY); } @@ -1668,7 +1736,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 +1745,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 +1760,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 +1773,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 +1834,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 +1850,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 + + 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), + 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 +1902,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 +1921,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 +1945,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 +2255,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..01ff35fc088c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -1142,6 +1142,20 @@ public class LocalDisplayAdapterTest { } @Test + public void test_createLocalExternalDisplay_displayManagementEnabled_doesNotCrash() + throws Exception { + FakeDisplay display = new FakeDisplay(PORT_A); + display.info.isInternal = false; + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token)).thenReturn(null); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + } + + @Test public void test_createLocalExternalDisplay_displayManagementEnabled_shouldHaveDefaultGroup() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); @@ -1246,6 +1260,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/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java index ad68de84eace..9d8d520fcb53 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java @@ -15,6 +15,8 @@ */ package com.android.server.power.batterysaver; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -35,12 +37,14 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.os.PowerManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings.Global; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; @@ -65,6 +69,9 @@ public class BatterySaverStateMachineTest { private DevicePersistedState mPersistedState; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT); + private class DevicePersistedState { // Current battery level. public int batteryLevel = 100; @@ -171,6 +178,11 @@ public class BatterySaverStateMachineTest { void triggerDynamicModeNotification() { // Do nothing } + + @Override + void triggerDynamicModeNotificationV2() { + // Do nothing + } } @Before 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/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index f971f0e6d4fb..4e8c75559f3b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -923,6 +923,8 @@ public class AccessibilityManagerServiceTest { ResolveInfo resolveInfo1 = installedService1.getResolveInfo(); AccessibilityServiceInfo installedService2 = mA11yms.getCurrentUserState().mInstalledServices.getLast(); + // Invokes client change to trigger onUserStateChanged. + mA11yms.onClientChangeLocked(false); // Disables `installedService2` when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) 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/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java index e756082bc912..758c84a26dcd 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AppOpsManager; import android.content.Context; import android.content.res.Resources; import android.media.AudioDeviceAttributes; @@ -37,6 +38,7 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.IAudioDeviceVolumeDispatcher; import android.media.VolumeInfo; +import android.os.PermissionEnforcer; import android.os.RemoteException; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -98,7 +100,8 @@ public class AbsoluteVolumeBehaviorTest { mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer, mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, - mTestLooper.getLooper()) { + mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class), + mock(AudioServerPermissionProvider.class)) { @Override public int getDeviceForStream(int stream) { return AudioSystem.DEVICE_OUT_SPEAKER; diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java index 3623012b348f..2cb02bdd2806 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java @@ -23,12 +23,14 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.app.AppOpsManager; import android.content.Context; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioSystem; import android.media.VolumeInfo; +import android.os.PermissionEnforcer; import android.os.test.TestLooper; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -75,7 +77,8 @@ public class AudioDeviceVolumeManagerTest { mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer, mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock, - mTestLooper.getLooper()) { + mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class), + mock(AudioServerPermissionProvider.class)) { @Override public int getDeviceForStream(int stream) { return AudioSystem.DEVICE_OUT_SPEAKER; diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java new file mode 100644 index 000000000000..8d772ad5c124 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java @@ -0,0 +1,308 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.media.permission.INativePermissionController; +import com.android.media.permission.UidPackageState; +import com.android.server.pm.pkg.PackageState; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public final class AudioServerPermissionProviderTest { + + // Class under test + private AudioServerPermissionProvider mPermissionProvider; + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock public INativePermissionController mMockPc; + + @Mock public PackageState mMockPackageStateOne_10000_one; + @Mock public PackageState mMockPackageStateTwo_10001_two; + @Mock public PackageState mMockPackageStateThree_10000_one; + @Mock public PackageState mMockPackageStateFour_10000_three; + @Mock public PackageState mMockPackageStateFive_10001_four; + @Mock public PackageState mMockPackageStateSix_10000_two; + + public List<UidPackageState> mInitPackageListExpected; + + // Argument matcher which matches that the state is equal even if the package names are out of + // order (since they are logically a set). + public static final class UidPackageStateMatcher implements ArgumentMatcher<UidPackageState> { + private final int mUid; + private final List<String> mSortedPackages; + + public UidPackageStateMatcher(int uid, List<String> packageNames) { + mUid = uid; + if (packageNames != null) { + mSortedPackages = new ArrayList(packageNames); + Collections.sort(mSortedPackages); + } else { + mSortedPackages = null; + } + } + + public UidPackageStateMatcher(UidPackageState toMatch) { + this(toMatch.uid, toMatch.packageNames); + } + + @Override + public boolean matches(UidPackageState state) { + if (state == null) return false; + if (state.uid != mUid) return false; + if ((state.packageNames == null) != (mSortedPackages == null)) return false; + var copy = new ArrayList(state.packageNames); + Collections.sort(copy); + return mSortedPackages.equals(copy); + } + + @Override + public String toString() { + return "Matcher for UidState with uid: " + mUid + ": " + mSortedPackages; + } + } + + public static final class PackageStateListMatcher + implements ArgumentMatcher<List<UidPackageState>> { + + private final List<UidPackageState> mToMatch; + + public PackageStateListMatcher(List<UidPackageState> toMatch) { + mToMatch = Objects.requireNonNull(toMatch); + } + + @Override + public boolean matches(List<UidPackageState> other) { + if (other == null) return false; + if (other.size() != mToMatch.size()) return false; + for (int i = 0; i < mToMatch.size(); i++) { + var matcher = new UidPackageStateMatcher(mToMatch.get(i)); + if (!matcher.matches(other.get(i))) return false; + } + return true; + } + + @Override + public String toString() { + return "Matcher for List<UidState> with uid: " + mToMatch; + } + } + + @Before + public void setup() { + when(mMockPackageStateOne_10000_one.getAppId()).thenReturn(10000); + when(mMockPackageStateOne_10000_one.getPackageName()).thenReturn("com.package.one"); + + when(mMockPackageStateTwo_10001_two.getAppId()).thenReturn(10001); + when(mMockPackageStateTwo_10001_two.getPackageName()).thenReturn("com.package.two"); + + // Same state as the first is intentional, emulating multi-user + when(mMockPackageStateThree_10000_one.getAppId()).thenReturn(10000); + when(mMockPackageStateThree_10000_one.getPackageName()).thenReturn("com.package.one"); + + when(mMockPackageStateFour_10000_three.getAppId()).thenReturn(10000); + when(mMockPackageStateFour_10000_three.getPackageName()).thenReturn("com.package.three"); + + when(mMockPackageStateFive_10001_four.getAppId()).thenReturn(10001); + when(mMockPackageStateFive_10001_four.getPackageName()).thenReturn("com.package.four"); + + when(mMockPackageStateSix_10000_two.getAppId()).thenReturn(10000); + when(mMockPackageStateSix_10000_two.getPackageName()).thenReturn("com.package.two"); + } + + @Test + public void testInitialPackagePopulation() throws Exception { + var initPackageListData = + List.of( + mMockPackageStateOne_10000_one, + mMockPackageStateTwo_10001_two, + mMockPackageStateThree_10000_one, + mMockPackageStateFour_10000_three, + mMockPackageStateFive_10001_four, + mMockPackageStateSix_10000_two); + var expectedPackageList = + List.of( + createUidPackageState( + 10000, + List.of("com.package.one", "com.package.two", "com.package.three")), + createUidPackageState( + 10001, List.of("com.package.two", "com.package.four"))); + + mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider.onServiceStart(mMockPc); + verify(mMockPc) + .populatePackagesForUids(argThat(new PackageStateListMatcher(expectedPackageList))); + } + + @Test + public void testOnModifyPackageState_whenNewUid() throws Exception { + // 10000: one | 10001: two + var initPackageListData = + List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); + mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider.onServiceStart(mMockPc); + + // new uid, including user component + mPermissionProvider.onModifyPackageState(1_10002, "com.package.new", false /* isRemove */); + + verify(mMockPc) + .updatePackagesForUid( + argThat(new UidPackageStateMatcher(10002, List.of("com.package.new")))); + verify(mMockPc).updatePackagesForUid(any()); // exactly once + } + + @Test + public void testOnModifyPackageState_whenRemoveUid() throws Exception { + // 10000: one | 10001: two + var initPackageListData = + List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); + mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider.onServiceStart(mMockPc); + + // Includes user-id + mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */); + + verify(mMockPc).updatePackagesForUid(argThat(new UidPackageStateMatcher(10000, List.of()))); + verify(mMockPc).updatePackagesForUid(any()); // exactly once + } + + @Test + public void testOnModifyPackageState_whenUpdatedUidAddition() throws Exception { + // 10000: one | 10001: two + var initPackageListData = + List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); + mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider.onServiceStart(mMockPc); + + // Includes user-id + mPermissionProvider.onModifyPackageState(1_10000, "com.package.new", false /* isRemove */); + + verify(mMockPc) + .updatePackagesForUid( + argThat( + new UidPackageStateMatcher( + 10000, List.of("com.package.one", "com.package.new")))); + verify(mMockPc).updatePackagesForUid(any()); // exactly once + } + + @Test + public void testOnModifyPackageState_whenUpdateUidRemoval() throws Exception { + // 10000: one, two | 10001: two + var initPackageListData = + List.of( + mMockPackageStateOne_10000_one, + mMockPackageStateTwo_10001_two, + mMockPackageStateSix_10000_two); + mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider.onServiceStart(mMockPc); + + // Includes user-id + mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */); + + verify(mMockPc) + .updatePackagesForUid( + argThat( + new UidPackageStateMatcher( + createUidPackageState(10000, List.of("com.package.two"))))); + verify(mMockPc).updatePackagesForUid(any()); // exactly once + } + + @Test + public void testOnServiceStart() throws Exception { + // 10000: one, two | 10001: two + var initPackageListData = + List.of( + mMockPackageStateOne_10000_one, + mMockPackageStateTwo_10001_two, + mMockPackageStateSix_10000_two); + mPermissionProvider = new AudioServerPermissionProvider(initPackageListData); + mPermissionProvider.onServiceStart(mMockPc); + mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */); + verify(mMockPc) + .updatePackagesForUid( + argThat(new UidPackageStateMatcher(10000, List.of("com.package.two")))); + + verify(mMockPc).updatePackagesForUid(any()); // exactly once + mPermissionProvider.onModifyPackageState( + 1_10000, "com.package.three", false /* isRemove */); + verify(mMockPc) + .updatePackagesForUid( + argThat( + new UidPackageStateMatcher( + 10000, List.of("com.package.two", "com.package.three")))); + verify(mMockPc, times(2)).updatePackagesForUid(any()); // exactly twice + // state is now 10000: two, three | 10001: two + + // simulate restart of the service + mPermissionProvider.onServiceStart(null); // should handle null + var newMockPc = mock(INativePermissionController.class); + mPermissionProvider.onServiceStart(newMockPc); + + var expectedPackageList = + List.of( + createUidPackageState( + 10000, List.of("com.package.two", "com.package.three")), + createUidPackageState(10001, List.of("com.package.two"))); + + verify(newMockPc) + .populatePackagesForUids(argThat(new PackageStateListMatcher(expectedPackageList))); + + verify(newMockPc, never()).updatePackagesForUid(any()); + // updates should still work after restart + mPermissionProvider.onModifyPackageState(10001, "com.package.four", false /* isRemove */); + verify(newMockPc) + .updatePackagesForUid( + argThat( + new UidPackageStateMatcher( + 10001, List.of("com.package.two", "com.package.four")))); + // exactly once + verify(newMockPc).updatePackagesForUid(any()); + } + + private static UidPackageState createUidPackageState(int uid, List<String> packages) { + var res = new UidPackageState(); + res.uid = uid; + res.packageNames = packages; + return res; + } +} diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java index 634877eb2539..037c3c00443c 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java @@ -66,6 +66,7 @@ public class AudioServiceTest { @Mock private AppOpsManager mMockAppOpsManager; @Mock private AudioPolicyFacade mMockAudioPolicy; @Mock private PermissionEnforcer mMockPermissionEnforcer; + @Mock private AudioServerPermissionProvider mMockPermissionProvider; // the class being unit-tested here private AudioService mAudioService; @@ -86,7 +87,7 @@ public class AudioServiceTest { .thenReturn(AppOpsManager.MODE_ALLOWED); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer, mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null, - mMockAppOpsManager, mMockPermissionEnforcer); + mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider); } /** diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java index 8dfcc1843fed..27b552fa7cdd 100644 --- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java @@ -22,11 +22,13 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Mockito.mock; import android.annotation.NonNull; +import android.app.AppOpsManager; import android.content.Context; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.IDeviceVolumeBehaviorDispatcher; +import android.os.PermissionEnforcer; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -75,7 +77,8 @@ public class DeviceVolumeBehaviorTest { mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer, mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock, - mTestLooper.getLooper()); + mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class), + mock(AudioServerPermissionProvider.class)); mTestLooper.dispatchAll(); } diff --git a/services/tests/servicestests/src/com/android/server/audio/ServiceHolderTest.java b/services/tests/servicestests/src/com/android/server/audio/ServiceHolderTest.java new file mode 100644 index 000000000000..39f19ae1b382 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/ServiceHolderTest.java @@ -0,0 +1,284 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +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.media.IAudioPolicyService; +import android.os.Binder; +import android.os.IBinder; +import android.os.IServiceCallback; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Function; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class ServiceHolderTest { + + private static final String AUDIO_POLICY_SERVICE_NAME = "media.audio_policy"; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + // the actual class under test + private ServiceHolder<IAudioPolicyService> mServiceHolder; + + @Mock private ServiceHolder.ServiceProviderFacade mServiceProviderFacade; + + @Mock private IAudioPolicyService mAudioPolicyService; + + @Mock private IBinder mBinder; + + @Mock private Consumer<IAudioPolicyService> mTaskOne; + @Mock private Consumer<IAudioPolicyService> mTaskTwo; + + @Before + public void setUp() throws Exception { + mServiceHolder = + new ServiceHolder( + AUDIO_POLICY_SERVICE_NAME, + (Function<IBinder, IAudioPolicyService>) + binder -> { + if (binder == mBinder) { + return mAudioPolicyService; + } else { + return mock(IAudioPolicyService.class); + } + }, + r -> r.run(), + mServiceProviderFacade); + when(mAudioPolicyService.asBinder()).thenReturn(mBinder); + } + + @Test + public void testListenerRegistered_whenConstructed() { + verify(mServiceProviderFacade) + .registerForNotifications(eq(AUDIO_POLICY_SERVICE_NAME), ArgumentMatchers.any()); + } + + @Test + public void testServiceSuccessfullyPopulated_whenCallback() throws RemoteException { + initializeViaCallback(); + verify(mBinder).linkToDeath(any(), anyInt()); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + } + + @Test + public void testCheckServiceCalled_whenUncached() { + when(mServiceProviderFacade.checkService(eq(AUDIO_POLICY_SERVICE_NAME))) + .thenReturn(mBinder); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + } + + @Test + public void testCheckServiceTransmitsNull() { + assertThat(mServiceHolder.checkService()).isEqualTo(null); + } + + @Test + public void testWaitForServiceCalled_whenUncached() { + when(mServiceProviderFacade.waitForService(eq(AUDIO_POLICY_SERVICE_NAME))) + .thenReturn(mBinder); + assertThat(mServiceHolder.waitForService()).isEqualTo(mAudioPolicyService); + } + + @Test + public void testCheckServiceNotCalled_whenCached() { + initializeViaCallback(); + mServiceHolder.checkService(); + verify(mServiceProviderFacade, never()).checkService(any()); + } + + @Test + public void testWaitForServiceNotCalled_whenCached() { + initializeViaCallback(); + mServiceHolder.waitForService(); + verify(mServiceProviderFacade, never()).waitForService(any()); + } + + @Test + public void testStartTaskCalled_onStart() { + mServiceHolder.registerOnStartTask(mTaskOne); + mServiceHolder.registerOnStartTask(mTaskTwo); + mServiceHolder.unregisterOnStartTask(mTaskOne); + when(mServiceProviderFacade.checkService(eq(AUDIO_POLICY_SERVICE_NAME))) + .thenReturn(mBinder); + + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + verify(mTaskTwo).accept(eq(mAudioPolicyService)); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testStartTaskCalled_onStartFromCallback() { + mServiceHolder.registerOnStartTask(mTaskOne); + mServiceHolder.registerOnStartTask(mTaskTwo); + mServiceHolder.unregisterOnStartTask(mTaskOne); + + initializeViaCallback(); + + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + verify(mTaskTwo).accept(eq(mAudioPolicyService)); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testStartTaskCalled_onRegisterAfterStarted() { + initializeViaCallback(); + mServiceHolder.registerOnStartTask(mTaskOne); + verify(mTaskOne).accept(eq(mAudioPolicyService)); + } + + @Test + public void testBinderDied_clearsServiceAndUnlinks() { + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + mServiceHolder.binderDied(mBinder); + + verify(mBinder).unlinkToDeath(any(), anyInt()); + assertThat(mServiceHolder.checkService()).isEqualTo(null); + verify(mServiceProviderFacade).checkService(eq(AUDIO_POLICY_SERVICE_NAME)); + } + + @Test + public void testBinderDied_callsDeathTasks() { + mServiceHolder.registerOnDeathTask(mTaskOne); + mServiceHolder.registerOnDeathTask(mTaskTwo); + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + mServiceHolder.unregisterOnDeathTask(mTaskOne); + + mServiceHolder.binderDied(mBinder); + + verify(mTaskTwo).accept(eq(mAudioPolicyService)); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testAttemptClear_clearsServiceAndUnlinks() { + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + mServiceHolder.attemptClear(mBinder); + + verify(mBinder).unlinkToDeath(any(), anyInt()); + assertThat(mServiceHolder.checkService()).isEqualTo(null); + verify(mServiceProviderFacade).checkService(eq(AUDIO_POLICY_SERVICE_NAME)); + } + + @Test + public void testAttemptClear_callsDeathTasks() { + mServiceHolder.registerOnDeathTask(mTaskOne); + mServiceHolder.registerOnDeathTask(mTaskTwo); + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + mServiceHolder.unregisterOnDeathTask(mTaskOne); + + mServiceHolder.attemptClear(mBinder); + + verify(mTaskTwo).accept(eq(mAudioPolicyService)); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testSet_whenServiceSet_isIgnored() { + mServiceHolder.registerOnStartTask(mTaskOne); + when(mServiceProviderFacade.checkService(eq(AUDIO_POLICY_SERVICE_NAME))) + .thenReturn(mBinder); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + verify(mTaskOne).accept(eq(mAudioPolicyService)); + + // get the callback + ArgumentCaptor<IServiceCallback> cb = ArgumentCaptor.forClass(IServiceCallback.class); + verify(mServiceProviderFacade) + .registerForNotifications(eq(AUDIO_POLICY_SERVICE_NAME), cb.capture()); + + // Simulate a service callback with a different instance + try { + cb.getValue().onRegistration(AUDIO_POLICY_SERVICE_NAME, new Binder()); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + // No additional start task call (i.e. only the first verify) + verify(mTaskOne).accept(any()); + // Same instance + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + } + + @Test + public void testClear_whenServiceCleared_isIgnored() { + mServiceHolder.registerOnDeathTask(mTaskOne); + mServiceHolder.attemptClear(mBinder); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testClear_withDifferentCookie_isIgnored() { + mServiceHolder.registerOnDeathTask(mTaskOne); + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + // Notif for stale cookie + mServiceHolder.attemptClear(new Binder()); + + // Service shouldn't be cleared + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + // No death tasks should fire + verify(mTaskOne, never()).accept(any()); + } + + private void initializeViaCallback() { + ArgumentCaptor<IServiceCallback> cb = ArgumentCaptor.forClass(IServiceCallback.class); + verify(mServiceProviderFacade) + .registerForNotifications(eq(AUDIO_POLICY_SERVICE_NAME), cb.capture()); + + try { + cb.getValue().onRegistration(AUDIO_POLICY_SERVICE_NAME, mBinder); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java index 23728db34c34..8e34ee1b6a42 100644 --- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java @@ -40,6 +40,7 @@ import static android.view.KeyEvent.ACTION_DOWN; import static android.view.KeyEvent.KEYCODE_VOLUME_UP; import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME; +import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -132,9 +133,12 @@ public class VolumeHelperTest { @Mock private PermissionEnforcer mMockPermissionEnforcer; @Mock + private AudioServerPermissionProvider mMockPermissionProvider; + @Mock private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; - private final AudioPolicyFacade mFakeAudioPolicy = lookbackAudio -> false; + @Mock + private AudioPolicyFacade mMockAudioPolicy; private AudioVolumeGroup mAudioMusicVolumeGroup; @@ -153,9 +157,10 @@ public class VolumeHelperTest { SystemServerAdapter systemServer, SettingsAdapter settings, AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, @Nullable Looper looper, AppOpsManager appOps, - @NonNull PermissionEnforcer enforcer) { + @NonNull PermissionEnforcer enforcer, + AudioServerPermissionProvider permissionProvider) { super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper, - audioPolicy, looper, appOps, enforcer); + audioPolicy, looper, appOps, enforcer, permissionProvider); } public void setDeviceForStream(int stream, int device) { @@ -209,8 +214,9 @@ public class VolumeHelperTest { mAm = mContext.getSystemService(AudioManager.class); mAudioService = new MyAudioService(mContext, mSpyAudioSystem, mSpySystemServer, - mSettingsAdapter, mAudioVolumeGroupHelper, mFakeAudioPolicy, - mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer); + mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, + mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer, + mMockPermissionProvider); mTestLooper.dispatchAll(); prepareAudioServiceState(); @@ -552,7 +558,7 @@ public class VolumeHelperTest { } @Test - @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) + @RequiresFlagsDisabled({FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME, FLAG_ABS_VOLUME_INDEX_FIX}) public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception { final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC); final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC); @@ -607,6 +613,7 @@ public class VolumeHelperTest { @Test @RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) + @RequiresFlagsDisabled(FLAG_ABS_VOLUME_INDEX_FIX) public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception { final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC); final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC); 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/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index 98e119cf0dad..473d1dc22d7a 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -139,12 +139,13 @@ public class HdmiCecMessageValidatorTest { @Test public void isValid_setSystemAudioMode() { - assertMessageValidity("40:72:00").isEqualTo(OK); - assertMessageValidity("4F:72:01:03").isEqualTo(OK); + assertMessageValidity("50:72:00").isEqualTo(OK); + assertMessageValidity("50:72:01").isEqualTo(OK); + assertMessageValidity("5F:72:01:03").isEqualTo(ERROR_PARAMETER_LONG); - assertMessageValidity("F0:72").isEqualTo(ERROR_SOURCE); - assertMessageValidity("40:72").isEqualTo(ERROR_PARAMETER_SHORT); - assertMessageValidity("40:72:02").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:72:00").isEqualTo(ERROR_SOURCE); + assertMessageValidity("50:72").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("50:72:02").isEqualTo(ERROR_PARAMETER); } @Test 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/HdmiUtilsTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java index c89c32a03553..74583dd619c7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java @@ -709,4 +709,18 @@ public class HdmiUtilsTest { assertThat(HdmiUtils.buildMessage("40:32:65:6E:67").getParams()).isEqualTo( new byte[]{0x65, 0x6E, 0x67}); } + + @Test + public void testVerifyAddressType() { + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_TV, + HdmiDeviceInfo.DEVICE_TV)); + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_AUDIO_SYSTEM, + HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)); + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_PLAYBACK_1, + HdmiDeviceInfo.DEVICE_PLAYBACK)); + assertFalse(HdmiUtils.verifyAddressType(Constants.ADDR_SPECIFIC_USE, + HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)); + assertFalse(HdmiUtils.verifyAddressType(Constants.ADDR_PLAYBACK_2, + HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)); + } } 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/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java index 7dd1847114c8..50cfa753ebdb 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java @@ -20,7 +20,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -52,6 +51,7 @@ import android.util.ArraySet; import android.util.SparseArray; import android.util.Xml; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.content.PackageMonitor; @@ -70,6 +70,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -95,21 +96,21 @@ public class LocaleManagerBackupRestoreTest { private static final int DEFAULT_USER_ID = 0; private static final int WORK_PROFILE_USER_ID = 10; private static final int DEFAULT_UID = Binder.getCallingUid() + 100; + private static final int WORK_PROFILE_UID = Binder.getCallingUid() + 1000100; private static final long DEFAULT_CREATION_TIME_MILLIS = 1000; private static final Duration RETENTION_PERIOD = Duration.ofDays(3); private static final LocaleList DEFAULT_LOCALES = LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS); private static final Map<String, LocalesInfo> DEFAULT_PACKAGE_LOCALES_INFO_MAP = Map.of( DEFAULT_PACKAGE_NAME, new LocalesInfo(DEFAULT_LOCALE_TAGS, false)); - private static final SparseArray<LocaleManagerBackupHelper.StagedData> STAGE_DATA = - new SparseArray<>(); + private final SparseArray<File> mStagedDataFiles = new SparseArray<>(); + private File mArchivedPackageFile; private LocaleManagerBackupHelper mBackupHelper; private long mCurrentTimeMillis; + private Context mContext = spy(ApplicationProvider.getApplicationContext()); @Mock - private Context mMockContext; - @Mock private PackageManager mMockPackageManager; @Mock private LocaleManagerService mMockLocaleManagerService; @@ -138,23 +139,28 @@ public class LocaleManagerBackupRestoreTest { @Before public void setUp() throws Exception { - mMockContext = mock(Context.class); mMockPackageManager = mock(PackageManager.class); mMockLocaleManagerService = mock(LocaleManagerService.class); mMockDelegateAppLocalePackages = mock(SharedPreferences.class); mMockSpEditor = mock(SharedPreferences.Editor.class); SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class); - doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); + doReturn(mMockPackageManager).when(mContext).getPackageManager(); doReturn(mMockSpEditor).when(mMockDelegateAppLocalePackages).edit(); HandlerThread broadcastHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); broadcastHandlerThread.start(); - mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext, - mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA, - broadcastHandlerThread, mMockDelegateAppLocalePackages)); + File file0 = new File(mContext.getCacheDir(), "file_user_0.txt"); + File file10 = new File(mContext.getCacheDir(), "file_user_10.txt"); + mStagedDataFiles.put(DEFAULT_USER_ID, file0); + mStagedDataFiles.put(WORK_PROFILE_USER_ID, file10); + mArchivedPackageFile = new File(mContext.getCacheDir(), "file_archived.txt"); + + mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mContext, + mMockLocaleManagerService, mMockPackageManager, mClock, broadcastHandlerThread, + mStagedDataFiles, mArchivedPackageFile, mMockDelegateAppLocalePackages)); doNothing().when(mBackupHelper).notifyBackupManager(); mUserMonitor = mBackupHelper.getUserMonitor(); @@ -165,7 +171,16 @@ public class LocaleManagerBackupRestoreTest { @After public void tearDown() throws Exception { - STAGE_DATA.clear(); + for (int i = 0; i < mStagedDataFiles.size(); i++) { + int userId = mStagedDataFiles.keyAt(i); + File file = mStagedDataFiles.get(userId); + SharedPreferences sp = mContext.getSharedPreferences(file, Context.MODE_PRIVATE); + sp.edit().clear().commit(); + if (file.exists()) { + file.delete(); + } + } + mStagedDataFiles.clear(); } @Test @@ -543,17 +558,21 @@ public class LocaleManagerBackupRestoreTest { mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle); mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle); + checkArchivedFileExists(); + mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); verifyNothingRestored(); setUpPackageInstalled(pkgNameA); - mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID); + mBackupHelper.onPackageUpdateFinished(pkgNameA, DEFAULT_UID); verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + checkArchivedFileExists(); + mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false); @@ -565,11 +584,12 @@ public class LocaleManagerBackupRestoreTest { setUpPackageInstalled(pkgNameB); - mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID); + mBackupHelper.onPackageUpdateFinished(pkgNameB, DEFAULT_UID); verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + checkArchivedFileDoesNotExist(); mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false); @@ -723,7 +743,7 @@ public class LocaleManagerBackupRestoreTest { Intent intent = new Intent(); intent.setAction(Intent.ACTION_USER_REMOVED); intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID); - mUserMonitor.onReceive(mMockContext, intent); + mUserMonitor.onReceive(mContext, intent); // Stage data should be removed only for DEFAULT_USER_ID. checkStageDataDoesNotExist(DEFAULT_USER_ID); @@ -732,6 +752,72 @@ public class LocaleManagerBackupRestoreTest { } @Test + public void testRestore_multipleProfile_restoresFromStage_ArchiveEnabled() throws Exception { + final ByteArrayOutputStream outDefault = new ByteArrayOutputStream(); + writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_INFO_MAP); + final ByteArrayOutputStream outWorkProfile = new ByteArrayOutputStream(); + String anotherPackage = "com.android.anotherapp"; + String anotherLangTags = "mr,zh"; + LocalesInfo localesInfo = new LocalesInfo(anotherLangTags, true); + HashMap<String, LocalesInfo> pkgLocalesMapWorkProfile = new HashMap<>(); + pkgLocalesMapWorkProfile.put(anotherPackage, localesInfo); + writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile); + // DEFAULT_PACKAGE_NAME is NOT installed on the device. + setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME); + setUpPackageNotInstalled(anotherPackage); + setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList()); + setUpLocalesForPackage(anotherPackage, LocaleList.getEmptyLocaleList()); + setUpPackageNamesForSp(new ArraySet<>()); + + Bundle bundle = new Bundle(); + bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true); + mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, bundle); + mPackageMonitor.onPackageAddedWithExtras(anotherPackage, WORK_PROFILE_UID, bundle); + + checkArchivedFileExists(); + + mBackupHelper.stageAndApplyRestoredPayload(outDefault.toByteArray(), DEFAULT_USER_ID); + mBackupHelper.stageAndApplyRestoredPayload(outWorkProfile.toByteArray(), + WORK_PROFILE_USER_ID); + + verifyNothingRestored(); + verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP, + DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); + verifyStageDataForUser(pkgLocalesMapWorkProfile, + DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID); + + setUpPackageInstalled(DEFAULT_PACKAGE_NAME); + mBackupHelper.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID); + + verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME, + DEFAULT_USER_ID, + LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS), false, FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + checkArchivedFileExists(); + checkStageDataDoesNotExist(DEFAULT_USER_ID); + + mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, false, + false); + + verify(mMockSpEditor, times(0)).putStringSet(anyString(), any()); + + setUpPackageInstalled(anotherPackage); + mBackupHelper.onPackageUpdateFinished(anotherPackage, WORK_PROFILE_UID); + + verify(mMockLocaleManagerService, times(1)).setApplicationLocales(anotherPackage, + WORK_PROFILE_USER_ID, + LocaleList.forLanguageTags(anotherLangTags), true, FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + checkArchivedFileDoesNotExist(); + + mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, anotherPackage, true, false); + + verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID), + new ArraySet<>(Arrays.asList(anotherPackage))); + checkStageDataDoesNotExist(WORK_PROFILE_USER_ID); + } + + @Test public void testPackageRemoved_noInfoInSp() throws Exception { String pkgNameA = "com.android.myAppA"; String pkgNameB = "com.android.myAppB"; @@ -858,10 +944,22 @@ public class LocaleManagerBackupRestoreTest { private void verifyStageDataForUser(Map<String, LocalesInfo> expectedPkgLocalesMap, long expectedCreationTimeMillis, int userId) { - LocaleManagerBackupHelper.StagedData stagedDataForUser = STAGE_DATA.get(userId); - assertNotNull(stagedDataForUser); - assertEquals(expectedCreationTimeMillis, stagedDataForUser.mCreationTimeMillis); - verifyStageData(expectedPkgLocalesMap, stagedDataForUser.mPackageStates); + SharedPreferences sp = mContext.getSharedPreferences(mStagedDataFiles.get(userId), + Context.MODE_PRIVATE); + assertTrue(sp.getAll().size() > 0); + assertEquals(expectedCreationTimeMillis, sp.getLong("staged_data_time", -1)); + verifyStageData(expectedPkgLocalesMap, sp); + } + + private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap, + SharedPreferences sp) { + for (String pkg : expectedPkgLocalesMap.keySet()) { + assertTrue(!sp.getString(pkg, "").isEmpty()); + String[] info = sp.getString(pkg, "").split(" s:"); + assertEquals(expectedPkgLocalesMap.get(pkg).mLocales, info[0]); + assertEquals(expectedPkgLocalesMap.get(pkg).mSetFromDelegate, + Boolean.parseBoolean(info[1])); + } } private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap, @@ -875,11 +973,19 @@ public class LocaleManagerBackupRestoreTest { } } - private static void checkStageDataExists(int userId) { - assertNotNull(STAGE_DATA.get(userId)); + private void checkStageDataExists(int userId) { + assertTrue(mStagedDataFiles.get(userId) != null && mStagedDataFiles.get(userId).exists()); + } + + private void checkStageDataDoesNotExist(int userId) { + assertTrue(mStagedDataFiles.get(userId) == null || !mStagedDataFiles.get(userId).exists()); + } + + private void checkArchivedFileExists() { + assertTrue(mArchivedPackageFile.exists()); } - private static void checkStageDataDoesNotExist(int userId) { - assertNull(STAGE_DATA.get(userId)); + private void checkArchivedFileDoesNotExist() { + assertTrue(!mArchivedPackageFile.exists()); } -} +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java index 9f7cbe3170f0..b46902d9904a 100644 --- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java +++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java @@ -22,6 +22,7 @@ import android.content.pm.PackageManager; import android.os.HandlerThread; import android.util.SparseArray; +import java.io.File; import java.time.Clock; /** @@ -33,9 +34,9 @@ public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper { ShadowLocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService, PackageManager packageManager, Clock clock, - SparseArray<LocaleManagerBackupHelper.StagedData> stagedData, - HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) { - super(context, localeManagerService, packageManager, clock, stagedData, - broadcastHandlerThread, delegateAppLocalePackages); + HandlerThread broadcastHandlerThread, SparseArray<File> stagedDataFiles, + File archivedPackagesFile, SharedPreferences delegateAppLocalePackages) { + super(context, localeManagerService, packageManager, clock, broadcastHandlerThread, + stagedDataFiles, archivedPackagesFile, delegateAppLocalePackages); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 4b22652a3f21..601a01624189 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -43,6 +43,8 @@ import android.app.PropertyInvalidatedCache; import android.content.Intent; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.service.gatekeeper.GateKeeperResponse; @@ -483,18 +485,31 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { setSecureFrpMode(true); try { mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID); - fail("Password shouldn't be changeable before FRP unlock"); + fail("Password shouldn't be changeable while FRP is active"); } catch (SecurityException e) { } } @Test - public void testSetCredentialPossibleInSecureFrpModeAfterSuw() throws RemoteException { + @DisableFlags(android.security.Flags.FLAG_FRP_ENFORCEMENT) + public void testSetCredentialPossibleInSecureFrpModeAfterSuw_FlagOff() throws RemoteException { setUserSetupComplete(true); setSecureFrpMode(true); setCredential(PRIMARY_USER_ID, newPassword("1234")); } @Test + @EnableFlags(android.security.Flags.FLAG_FRP_ENFORCEMENT) + public void testSetCredentialNotPossibleInSecureFrpModeAfterSuw_FlagOn() + throws RemoteException { + setUserSetupComplete(true); + setSecureFrpMode(true); + try { + mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID); + fail("Password shouldn't be changeable after SUW while FRP is active"); + } catch (SecurityException e) { } + } + + @Test public void testPasswordHistoryDisabledByDefault() throws Exception { final int userId = PRIMARY_USER_ID; checkPasswordHistoryLength(userId, 0); diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index abd3abee82fb..e64397d4223b 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -372,8 +372,8 @@ public class MediaProjectionManagerServiceTest { doReturn(true) .when(mWindowManagerInternal) .setContentRecordingSession(any(ContentRecordingSession.class)); - ContentRecordingSession taskSession = - createTaskSession(mock(IBinder.class), targetUid); + ContentRecordingSession taskSession = createTaskSession(mock(IBinder.class)); + taskSession.setTargetUid(targetUid); service.setContentRecordingSession(taskSession); projection.stop(); @@ -708,8 +708,8 @@ public class MediaProjectionManagerServiceTest { mService = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); - ContentRecordingSession taskSession = - createTaskSession(mock(IBinder.class), targetUid); + ContentRecordingSession taskSession = createTaskSession(mock(IBinder.class)); + taskSession.setTargetUid(targetUid); mService.setContentRecordingSession(taskSession); MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); @@ -915,8 +915,8 @@ public class MediaProjectionManagerServiceTest { .setContentRecordingSession(any(ContentRecordingSession.class)); int targetUid = 123455; - ContentRecordingSession taskSession = - createTaskSession(mock(IBinder.class), targetUid); + ContentRecordingSession taskSession = createTaskSession(mock(IBinder.class)); + taskSession.setTargetUid(targetUid); service.setContentRecordingSession(taskSession); verify(mMediaProjectionMetricsLogger).logInProgress(projection.uid, targetUid); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java index d04c51819377..f6e1162a5ada 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java @@ -16,6 +16,10 @@ package com.android.server.notification; +import static android.service.notification.Condition.SOURCE_USER_ACTION; +import static android.service.notification.Condition.STATE_FALSE; +import static android.service.notification.Condition.STATE_TRUE; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -27,16 +31,20 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.app.Flags; import android.content.ComponentName; import android.content.ServiceConnection; import android.content.pm.IPackageManager; import android.net.Uri; import android.os.IInterface; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.Condition; import com.android.server.UiServiceTestCase; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -52,6 +60,10 @@ public class ConditionProvidersTest extends UiServiceTestCase { @Mock private ConditionProviders.Callback mCallback; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( + SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -67,8 +79,8 @@ public class ConditionProvidersTest extends UiServiceTestCase { ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo( mock(IInterface.class), cn, 0, false, mock(ServiceConnection.class), 33, 100); Condition[] conditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE), - new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE) + new Condition(Uri.parse("a"), "summary", STATE_TRUE), + new Condition(Uri.parse("b"), "summary2", STATE_TRUE) }; mProviders.notifyConditions("package", msi, conditions); @@ -85,9 +97,9 @@ public class ConditionProvidersTest extends UiServiceTestCase { mock(IInterface.class), new ComponentName("package", "cls"), 0, false, mock(ServiceConnection.class), 33, 100); Condition[] conditionsToNotify = new Condition[] { - new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE), - new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE), - new Condition(Uri.parse("c"), "summary3", Condition.STATE_TRUE) + new Condition(Uri.parse("a"), "summary", STATE_TRUE), + new Condition(Uri.parse("b"), "summary2", STATE_TRUE), + new Condition(Uri.parse("c"), "summary3", STATE_TRUE) }; mProviders.notifyConditions("package", msi, conditionsToNotify); @@ -104,10 +116,10 @@ public class ConditionProvidersTest extends UiServiceTestCase { mock(IInterface.class), new ComponentName("package", "cls"), 0, false, mock(ServiceConnection.class), 33, 100); Condition[] conditionsToNotify = new Condition[] { - new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE), - new Condition(Uri.parse("b"), "summary2", Condition.STATE_TRUE), - new Condition(Uri.parse("a"), "summary3", Condition.STATE_FALSE), - new Condition(Uri.parse("a"), "summary4", Condition.STATE_FALSE) + new Condition(Uri.parse("a"), "summary", STATE_TRUE), + new Condition(Uri.parse("b"), "summary2", STATE_TRUE), + new Condition(Uri.parse("a"), "summary3", STATE_FALSE), + new Condition(Uri.parse("a"), "summary4", STATE_FALSE) }; mProviders.notifyConditions("package", msi, conditionsToNotify); @@ -124,10 +136,10 @@ public class ConditionProvidersTest extends UiServiceTestCase { mock(IInterface.class), new ComponentName("package", "cls"), 0, false, mock(ServiceConnection.class), 33, 100); Condition[] conditionsToNotify = new Condition[] { - new Condition(Uri.parse("a"), "summary", Condition.STATE_TRUE), + new Condition(Uri.parse("a"), "summary", STATE_TRUE), null, null, - new Condition(Uri.parse("b"), "summary", Condition.STATE_TRUE) + new Condition(Uri.parse("b"), "summary", STATE_TRUE) }; mProviders.notifyConditions("package", msi, conditionsToNotify); @@ -138,6 +150,57 @@ public class ConditionProvidersTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_MODES_UI) + public void notifyConditions_appCannotUndoUserEnablement() { + ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo( + mock(IInterface.class), new ComponentName("package", "cls"), 0, false, + mock(ServiceConnection.class), 33, 100); + // First, user enabled mode + Condition[] userConditions = new Condition[] { + new Condition(Uri.parse("a"), "summary", STATE_TRUE, SOURCE_USER_ACTION) + }; + mProviders.notifyConditions("package", msi, userConditions); + verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); + + // Second, app tries to disable it, but cannot + Condition[] appConditions = new Condition[] { + new Condition(Uri.parse("a"), "summary", STATE_FALSE) + }; + mProviders.notifyConditions("package", msi, appConditions); + verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + public void notifyConditions_appCanTakeoverUserEnablement() { + ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo( + mock(IInterface.class), new ComponentName("package", "cls"), 0, false, + mock(ServiceConnection.class), 33, 100); + // First, user enabled mode + Condition[] userConditions = new Condition[] { + new Condition(Uri.parse("a"), "summary", STATE_TRUE, SOURCE_USER_ACTION) + }; + mProviders.notifyConditions("package", msi, userConditions); + verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); + + // Second, app now thinks the rule should be on due it its intelligence + Condition[] appConditions = new Condition[] { + new Condition(Uri.parse("a"), "summary", STATE_TRUE) + }; + mProviders.notifyConditions("package", msi, appConditions); + verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(appConditions[0])); + + // Lastly, app can turn rule off when its intelligence think it should be off + appConditions = new Condition[] { + new Condition(Uri.parse("a"), "summary", STATE_FALSE) + }; + mProviders.notifyConditions("package", msi, appConditions); + verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(appConditions[0])); + + verifyNoMoreInteractions(mCallback); + } + + @Test public void testRemoveDefaultFromConfig() { final int userId = 0; ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); 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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 70319754253b..4a9760bc3317 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2057,7 +2057,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWithTask(); // TaskSnapshotSurface requires a fullscreen opaque window. final WindowManager.LayoutParams params = new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); + TYPE_BASE_APPLICATION); params.width = params.height = WindowManager.LayoutParams.MATCH_PARENT; final TestWindowState w = new TestWindowState( mAtm.mWindowManager, getTestSession(), new TestIWindow(), params, activity); @@ -2504,25 +2504,6 @@ public class ActivityRecordTests extends WindowTestsBase { activity.removeImmediately(); } - @Test - @Presubmit - public void testGetTopFullscreenOpaqueWindow() { - final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); - assertNull(activity.getTopFullscreenOpaqueWindow()); - - final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "window1"); - final WindowState window11 = createWindow(null, TYPE_APPLICATION, activity, "window11"); - final WindowState window12 = createWindow(null, TYPE_APPLICATION, activity, "window12"); - assertEquals(window12, activity.getTopFullscreenOpaqueWindow()); - window12.mAttrs.width = 500; - assertEquals(window11, activity.getTopFullscreenOpaqueWindow()); - window11.mAttrs.width = 500; - assertEquals(window1, activity.getTopFullscreenOpaqueWindow()); - window1.mAttrs.alpha = 0f; - assertNull(activity.getTopFullscreenOpaqueWindow()); - activity.removeImmediately(); - } - @SetupWindows(addWindows = W_ACTIVITY) @Test public void testLandscapeSeascapeRotationByApp() { 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/PhysicalDisplaySwitchTransitionLauncherTest.java b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java index 402b704c1681..78509dbfdb1a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java @@ -273,7 +273,7 @@ public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase if (enabled) { mTransitionController.registerTransitionPlayer(mPlayer, null /* proc */); } else { - mTransitionController.detachPlayer(); + mTransitionController.unregisterTransitionPlayer(mPlayer); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 6b605ec6d0c0..7ced9d50ab3f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -107,7 +107,9 @@ import android.graphics.Rect; import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; 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 +190,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 +399,56 @@ public class SizeCompatTests extends WindowTestsBase { verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox(); } + // TODO(b/333663877): Enable test after fix + @Test + @RequiresFlagsDisabled({Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION}) + @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING) + 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); @@ -4008,6 +4061,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING) public void testImmersiveLetterboxAlignedToBottom_OverlappingNavbar() { assertLandscapeActivityAlignedToBottomWithNavbar(true /* immersive */); } @@ -4034,8 +4088,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/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 80c066debf1a..69f2d684fd77 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1446,6 +1446,33 @@ public class TransitionTests extends WindowTestsBase { } @Test + public void testTransitionEndedListeners() { + final TransitionController controller = new TestTransitionController(mAtm); + controller.setSyncEngine(mWm.mSyncEngine); + final ITransitionPlayer player = new ITransitionPlayer.Default(); + controller.registerTransitionPlayer(player, null /* playerProc */); + final Runnable transitionEndedListener = mock(Runnable.class); + + final Transition transition1 = controller.createTransition(TRANSIT_OPEN); + transition1.addTransitionEndedListener(transitionEndedListener); + + // Using abort to force-finish the sync (since we can't wait for drawing in unit test). + // We didn't call abort on the transition itself, so it will still run onTransactionReady + // normally. + mWm.mSyncEngine.abort(transition1.getSyncId()); + transition1.finishTransition(); + + verify(transitionEndedListener).run(); + + clearInvocations(transitionEndedListener); + + final Transition transition2 = controller.createTransition(TRANSIT_OPEN); + transition2.addTransitionEndedListener(transitionEndedListener); + transition2.abort(); + verify(transitionEndedListener).run(); + } + + @Test public void testTransientLaunch() { spyOn(mWm.mSnapshotController.mTaskSnapshotController); final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>(); @@ -1549,8 +1576,10 @@ public class TransitionTests extends WindowTestsBase { }); assertTrue(activity1.isVisible()); doReturn(false).when(task1).isTranslucent(null); + doReturn(false).when(task1).isTranslucentForTransition(); assertTrue(controller.canApplyDim(task1)); doReturn(true).when(task1).isTranslucent(null); + doReturn(true).when(task1).isTranslucentForTransition(); assertFalse(controller.canApplyDim(task1)); controller.finishTransition(closeTransition); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 7e6301fda872..24ebad60a191 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -795,39 +795,55 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() { - WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null); - assertThat(wci).isNull(); - } - - @Test - public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() { + public void testGetTaskWindowContainerTokenForRecordingSession_invalidCookie() { Binder cookie = new Binder("test cookie"); - WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession( + ContentRecordingSession.createTaskSession(cookie)); assertThat(wci).isNull(); final ActivityRecord testActivity = new ActivityBuilder(mAtm) .setCreateTask(true) .build(); - wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + wci = mWm.getTaskWindowContainerInfoForRecordingSession( + ContentRecordingSession.createTaskSession(cookie)); assertThat(wci).isNull(); } @Test - public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() { + public void testGetTaskWindowContainerTokenForRecordingSession_validCookie() { final Binder cookie = new Binder("ginger cookie"); final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); final int uid = 123; setupActivityWithLaunchCookie(cookie, launchRootTask, uid); - WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession( + ContentRecordingSession.createTaskSession(cookie)); + mExpect.that(wci.getToken()).isEqualTo(launchRootTask); + mExpect.that(wci.getUid()).isEqualTo(uid); + } + + @Test + public void testGetTaskWindowContainerTokenForRecordingSession_validTaskId() { + final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); + final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class); + when(remoteToken.toWindowContainerToken()).thenReturn(launchRootTask); + + final int uid = 123; + final ActivityRecord testActivity = + new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build(); + testActivity.mLaunchCookie = null; + testActivity.getTask().mRemoteToken = remoteToken; + + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession( + ContentRecordingSession.createTaskSession( + new Binder("cookie"), testActivity.getTask().mTaskId)); mExpect.that(wci.getToken()).isEqualTo(launchRootTask); mExpect.that(wci.getUid()).isEqualTo(uid); } @Test - public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() { + public void testGetTaskWindowContainerTokenForRecordingSession_multipleCookies() { final Binder cookie1 = new Binder("ginger cookie"); final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class); final int uid1 = 123; @@ -839,13 +855,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), mock(WindowContainerToken.class), /* uid= */ 789); - WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession( + ContentRecordingSession.createTaskSession(cookie1)); mExpect.that(wci.getToken()).isEqualTo(launchRootTask1); mExpect.that(wci.getUid()).isEqualTo(uid1); } @Test - public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() { + public void testGetTaskWindowContainerTokenForRecordingSession_multipleCookies_noneValid() { setupActivityWithLaunchCookie(new Binder("ginger cookie"), mock(WindowContainerToken.class), /* uid= */ 123); @@ -855,8 +872,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), mock(WindowContainerToken.class), /* uid= */ 789); - WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie( - new Binder("some other cookie")); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForRecordingSession( + ContentRecordingSession.createTaskSession(new Binder("some other cookie"))); assertThat(wci).isNull(); } @@ -895,6 +912,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() { WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay)); + activityRecord.mLaunchCookie = new Binder(); ContentRecordingSession session = ContentRecordingSession.createTaskSession( activityRecord.mLaunchCookie); @@ -908,6 +926,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); Task task = createTask(mDefaultDisplay); ActivityRecord activityRecord = createActivityRecord(task); + activityRecord.mLaunchCookie = new Binder(); ContentRecordingSession session = ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie); @@ -915,7 +934,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { mExpect.that(session.getTokenToRecord()) .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder()); - mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid()); + mExpect.that(session.getTargetUid()).isEqualTo(task.effectiveUid); } @Test diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 1d014201cf46..a7dbecbb5255 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -794,9 +794,8 @@ public class VoiceInteractionManagerService extends SystemService { if (curService != null && !curService.isEmpty()) { try { serviceComponent = ComponentName.unflattenFromString(curService); - serviceInfo = AppGlobals.getPackageManager() - .getServiceInfo(serviceComponent, 0, mCurUser); - } catch (RuntimeException | RemoteException e) { + serviceInfo = getValidVoiceInteractionServiceInfo(serviceComponent); + } catch (RuntimeException e) { Slog.wtf(TAG, "Bad voice interaction service name " + curService, e); serviceComponent = null; serviceInfo = null; @@ -834,6 +833,27 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Nullable + private ServiceInfo getValidVoiceInteractionServiceInfo( + @Nullable ComponentName serviceComponent) { + if (serviceComponent == null) { + return null; + } + List<ResolveInfo> services = queryInteractorServices( + mCurUser, serviceComponent.getPackageName()); + for (int i = 0; i < services.size(); i++) { + ResolveInfo service = services.get(i); + VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo( + mContext.getPackageManager(), service.serviceInfo); + ServiceInfo candidateInfo = info.getServiceInfo(); + if (candidateInfo != null + && candidateInfo.getComponentName().equals(serviceComponent)) { + return candidateInfo; + } + } + return null; + } + private List<ResolveInfo> queryInteractorServices( @UserIdInt int user, @Nullable String packageName) { diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index 2435243f0044..8fe107cc1ad3 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -716,6 +716,31 @@ public class Annotation { }) public @interface NetCapability { } + + /** + * Representing the transport type. Apps should generally not care about transport. A + * request for a fast internet connection could be satisfied by a number of different + * transports. If any are specified here it will be satisfied a Network that matches + * any of them. If a caller doesn't care about the transport it should not specify any. + * Must update here when new capabilities are added in {@link NetworkCapabilities}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TRANSPORT_" }, value = { + NetworkCapabilities.TRANSPORT_CELLULAR, + NetworkCapabilities.TRANSPORT_WIFI, + NetworkCapabilities.TRANSPORT_BLUETOOTH, + NetworkCapabilities.TRANSPORT_ETHERNET, + NetworkCapabilities.TRANSPORT_VPN, + NetworkCapabilities.TRANSPORT_WIFI_AWARE, + NetworkCapabilities.TRANSPORT_LOWPAN, + NetworkCapabilities.TRANSPORT_TEST, + NetworkCapabilities.TRANSPORT_USB, + NetworkCapabilities.TRANSPORT_THREAD, + NetworkCapabilities.TRANSPORT_SATELLITE, + }) + public @interface ConnectivityTransport { } + + /** * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate * android.net.NetworkAgent.ValidationStatus here. Must update here when new validation status diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp index 2cdf54248ebc..e09fbf6adc02 100644 --- a/tests/FlickerTests/ActivityEmbedding/Android.bp +++ b/tests/FlickerTests/ActivityEmbedding/Android.bp @@ -20,17 +20,65 @@ package { // all of the 'license_kinds' from "frameworks_base_license" // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 + default_team: "trendy_team_windowing_sdk", default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "FlickerTestsOther", +filegroup { + name: "FlickerTestsOtherCommon-src", + srcs: ["src/**/ActivityEmbeddingTestBase.kt"], +} + +filegroup { + name: "FlickerTestsOtherOpen-src", + srcs: ["src/**/open/*"], +} + +filegroup { + name: "FlickerTestsOtherRotation-src", + srcs: ["src/**/rotation/*"], +} + +java_library { + name: "FlickerTestsOtherCommon", + defaults: ["FlickerTestsDefault"], + srcs: [":FlickerTestsOtherCommon-src"], + static_libs: ["FlickerTestsBase"], +} + +java_defaults { + name: "FlickerTestsOtherDefaults", defaults: ["FlickerTestsDefault"], manifest: "AndroidManifest.xml", package_name: "com.android.server.wm.flicker", instrumentation_target_package: "com.android.server.wm.flicker", test_config_template: "AndroidTestTemplate.xml", - srcs: ["src/**/*"], - static_libs: ["FlickerTestsBase"], + static_libs: [ + "FlickerTestsBase", + "FlickerTestsOtherCommon", + ], data: ["trace_config/*"], } + +android_test { + name: "FlickerTestsOtherOpen", + defaults: ["FlickerTestsOtherDefaults"], + srcs: [":FlickerTestsOtherOpen-src"], +} + +android_test { + name: "FlickerTestsOtherRotation", + defaults: ["FlickerTestsOtherDefaults"], + srcs: [":FlickerTestsOtherRotation-src"], +} + +android_test { + name: "FlickerTestsOther", + defaults: ["FlickerTestsOtherDefaults"], + srcs: ["src/**/*"], + exclude_srcs: [ + ":FlickerTestsOtherOpen-src", + ":FlickerTestsOtherRotation-src", + ":FlickerTestsOtherCommon-src", + ], +} 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/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt index 8a241de32a2b..209a14b3657d 100644 --- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt +++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.service import android.app.Instrumentation +import android.platform.test.rule.DisableNotificationCooldownSettingRule import android.platform.test.rule.NavigationModeRule import android.platform.test.rule.PressHomeRule import android.platform.test.rule.UnlockScreenRule @@ -48,6 +49,7 @@ object Utils { clearCacheAfterParsing = false ) ) + .around(DisableNotificationCooldownSettingRule()) .around(PressHomeRule()) } } diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp index 3538949cbc8d..ccc3683f0b93 100644 --- a/tests/FlickerTests/IME/Android.bp +++ b/tests/FlickerTests/IME/Android.bp @@ -39,6 +39,10 @@ android_test { defaults: ["FlickerTestsDefault"], manifest: "AndroidManifest.xml", test_config_template: "AndroidTestTemplate.xml", + test_suites: [ + "device-tests", + "device-platinum-tests", + ], srcs: ["src/**/*"], static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt index dc5013519dbf..ed6e8df3e293 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt @@ -23,6 +23,7 @@ import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.traces.component.ComponentNameMatcher +import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper import org.junit.FixMethodOrder @@ -77,6 +78,7 @@ class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: LegacyFlickerTest) : @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible() + @FlakyTest(bugId = 330486656) @Presubmit @Test fun imeAppLayerIsAlwaysVisible() { diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt index c29e71ce4c79..07fc2300286a 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.notification import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.platform.test.rule.DisableNotificationCooldownSettingRule import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.FlickerTestData @@ -37,6 +38,7 @@ import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd import org.junit.Assume +import org.junit.ClassRule import org.junit.FixMethodOrder import org.junit.Ignore import org.junit.Test @@ -208,5 +210,10 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + + /** Ensures that posted notifications will alert and HUN even just after boot. */ + @ClassRule + @JvmField + val disablenotificationCooldown = DisableNotificationCooldownSettingRule() } } 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/tests/InputScreenshotTest/robotests/Android.bp b/tests/InputScreenshotTest/robotests/Android.bp index 384f58aac15f..d63fd69ae4d9 100644 --- a/tests/InputScreenshotTest/robotests/Android.bp +++ b/tests/InputScreenshotTest/robotests/Android.bp @@ -69,4 +69,6 @@ android_robolectric_test { upstream: true, java_resource_dirs: ["config"], instrumentation_for: "InputRoboApp", + + strict_mode: false, } diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 093923f3ed53..a8b383cd4274 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -101,8 +101,8 @@ public class PackageWatchdogTest { private static final String OBSERVER_NAME_2 = "observer2"; private static final String OBSERVER_NAME_3 = "observer3"; private static final String OBSERVER_NAME_4 = "observer4"; - private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); - private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); + private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(10); + private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(50); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -1453,7 +1453,8 @@ public class PackageWatchdogTest { raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); - moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS); + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + - TimeUnit.MINUTES.toMillis(1)); // The first failure will be outside the threshold. raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, @@ -1712,6 +1713,9 @@ public class PackageWatchdogTest { watchdog.onPackageFailure(packages, failureReason); } mTestLooper.dispatchAll(); + if (Flags.recoverabilityDetection()) { + moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS); + } } private PackageWatchdog createWatchdog() { 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) } |