Merge "Allow Overrides on CompatChange"
diff --git a/Android.bp b/Android.bp
index cfab18e..ea7ced9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -224,6 +224,7 @@
"android.hardware.radio.messaging-V2-java",
"android.hardware.radio.modem-V2-java",
"android.hardware.radio.network-V2-java",
+ "android.hardware.radio.satellite-V1-java",
"android.hardware.radio.sim-V2-java",
"android.hardware.radio.voice-V2-java",
"android.hardware.thermal-V1.0-java-constants",
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 64ba6d1..7bd5921 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -223,6 +223,27 @@
}
}
+ /** Tests creating and starting a new user. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void createAndStartUser_realistic() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ Log.d(TAG, "Starting timer");
+ final int userId = createUserNoFlags();
+
+ // Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
+ // ACTION_USER_STARTED.
+ runThenWaitForBroadcasts(userId, () -> {
+ mIam.startUserInBackground(userId);
+ }, Intent.ACTION_USER_STARTED);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/**
* Tests starting an uninitialized user.
* Measures the time until ACTION_USER_STARTED is received.
@@ -299,6 +320,29 @@
}
}
+ /**
+ * Tests starting & unlocking an uninitialized user.
+ * Measures the time until unlock listener is triggered and user is unlocked.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void startAndUnlockUser_realistic() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createUserNoFlags();
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ // Waits for UserState.mUnlockProgress.finish().
+ startUserInBackgroundAndWaitForUnlock(userId);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests switching to an uninitialized user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void switchUser() throws Exception {
@@ -527,7 +571,7 @@
removeUser(userId);
}
- /** Tests reaching LOOKED_BOOT_COMPLETE when switching to uninitialized user. */
+ /** Tests reaching LOCKED_BOOT_COMPLETE when switching to uninitialized user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void lockedBootCompleted() throws RemoteException {
while (mRunner.keepRunning()) {
@@ -550,6 +594,29 @@
}
}
+ /** Tests reaching LOCKED_BOOT_COMPLETE when switching to uninitialized user. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void lockedBootCompleted_realistic() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int startUser = ActivityManager.getCurrentUser();
+ final int userId = createUserNoFlags();
+
+ waitCoolDownPeriod();
+ mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+ mAm.switchUser(userId);
+ }, () -> fail("Failed to achieve onLockedBootComplete for user " + userId));
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ switchUserNoCheck(startUser);
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests stopping an ephemeral foreground user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void ephemeralUserStopped() throws RemoteException {
@@ -579,6 +646,33 @@
}
}
+ /** Tests stopping an ephemeral foreground user. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void ephemeralUserStopped_realistic() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int startUser = ActivityManager.getCurrentUser();
+ final int userId = createUserWithFlags(UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
+ runThenWaitForBroadcasts(userId, () -> {
+ switchUser(userId);
+ }, Intent.ACTION_MEDIA_MOUNTED);
+
+ waitCoolDownPeriod();
+ mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
+ runThenWaitForBroadcasts(userId, () -> {
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ mAm.switchUser(startUser);
+ }, Intent.ACTION_USER_STOPPED);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ }, null);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests creating a new profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void managedProfileCreate() throws RemoteException {
@@ -596,6 +690,24 @@
}
}
+ /** Tests creating a new profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void managedProfileCreate_realistic() throws RemoteException {
+ assumeTrue(mHasManagedUserFeature);
+
+ while (mRunner.keepRunning()) {
+ Log.d(TAG, "Starting timer");
+ final int userId = createManagedProfile();
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId));
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests starting (unlocking) an uninitialized profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void managedProfileUnlock() throws RemoteException {
@@ -616,6 +728,27 @@
}
}
+ /** Tests starting (unlocking) an uninitialized profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void managedProfileUnlock_realistic() throws RemoteException {
+ assumeTrue(mHasManagedUserFeature);
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createManagedProfile();
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ startUserInBackgroundAndWaitForUnlock(userId);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests starting (unlocking) a previously-started, but no-longer-running, profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void managedProfileUnlock_stopped() throws RemoteException {
@@ -639,6 +772,29 @@
}
}
+ /** Tests starting (unlocking) a previously-started, but no-longer-running, profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void managedProfileUnlock_stopped_realistic() throws RemoteException {
+ assumeTrue(mHasManagedUserFeature);
+ final int userId = createManagedProfile();
+ // Start the profile initially, then stop it. Similar to setQuietModeEnabled.
+ startUserInBackgroundAndWaitForUnlock(userId);
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ stopUserAfterWaitingForBroadcastIdle(userId, true);
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ startUserInBackgroundAndWaitForUnlock(userId);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ removeUser(userId);
+ }
+
/**
* Tests starting (unlocking) & launching an already-installed app in an uninitialized profile.
*/
@@ -665,6 +821,32 @@
}
/**
+ * Tests starting (unlocking) & launching an already-installed app in an uninitialized profile.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void managedProfileUnlockAndLaunchApp_realistic() throws RemoteException {
+ assumeTrue(mHasManagedUserFeature);
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createManagedProfile();
+ WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ startUserInBackgroundAndWaitForUnlock(userId);
+ startApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
+ /**
* Tests starting (unlocking) and launching a previously-launched app
* in a previously-started, but no-longer-running, profile.
* A sort of combination of {@link #managedProfileUnlockAndLaunchApp} and
@@ -696,6 +878,39 @@
}
}
+ /**
+ * Tests starting (unlocking) and launching a previously-launched app
+ * in a previously-started, but no-longer-running, profile.
+ * A sort of combination of {@link #managedProfileUnlockAndLaunchApp} and
+ * {@link #managedProfileUnlock_stopped}}.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void managedProfileUnlockAndLaunchApp_stopped_realistic() throws RemoteException {
+ assumeTrue(mHasManagedUserFeature);
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createManagedProfile();
+ WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+ startUserInBackgroundAndWaitForUnlock(userId);
+ startApp(userId, DUMMY_PACKAGE_NAME);
+ stopUserAfterWaitingForBroadcastIdle(userId, true);
+ SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ startUserInBackgroundAndWaitForUnlock(userId);
+ startApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests installing a pre-existing app in an uninitialized profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void managedProfileInstall() throws RemoteException {
@@ -716,6 +931,27 @@
}
}
+ /** Tests installing a pre-existing app in an uninitialized profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void managedProfileInstall_realistic() throws RemoteException {
+ assumeTrue(mHasManagedUserFeature);
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createManagedProfile();
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/**
* Tests creating a new profile, starting (unlocking) it, installing an app,
* and launching that app in it.
@@ -742,6 +978,33 @@
}
}
+ /**
+ * Tests creating a new profile, starting (unlocking) it, installing an app,
+ * and launching that app in it.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void managedProfileCreateUnlockInstallAndLaunchApp_realistic() throws RemoteException {
+ assumeTrue(mHasManagedUserFeature);
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ final int userId = createManagedProfile();
+ startUserInBackgroundAndWaitForUnlock(userId);
+ installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
+ startApp(userId, DUMMY_PACKAGE_NAME);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests stopping a profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void managedProfileStopped() throws RemoteException {
@@ -766,6 +1029,30 @@
}
}
+ /** Tests stopping a profile. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void managedProfileStopped_realistic() throws RemoteException {
+ assumeTrue(mHasManagedUserFeature);
+ final int userId = createManagedProfile();
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+
+ runThenWaitForBroadcasts(userId, () -> {
+ startUserInBackgroundAndWaitForUnlock(userId);
+ }, Intent.ACTION_MEDIA_MOUNTED);
+ waitCoolDownPeriod();
+ mRunner.resumeTiming();
+ Log.d(TAG, "Starting timer");
+
+ stopUser(userId, true);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ mRunner.resumeTimingForNextIteration();
+ }
+ removeUser(userId);
+ }
+
// TODO: This is just a POC. Do this properly and add more.
/** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index bf8984f..62d97358 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -158,33 +158,37 @@
* state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user.
*/
static final int WORK_TYPE_FGS = 1 << 1;
+ /** The job is allowed to run as a user-initiated job for a currently active user. */
+ static final int WORK_TYPE_UI = 1 << 2;
/** The job is allowed to run as an expedited job for a currently active user. */
- static final int WORK_TYPE_EJ = 1 << 2;
+ static final int WORK_TYPE_EJ = 1 << 3;
/**
* The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
* {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so
* can run as a background job.
*/
- static final int WORK_TYPE_BG = 1 << 3;
+ static final int WORK_TYPE_BG = 1 << 4;
/**
* The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
- * state, or is allowed to run as an expedited job, but is for a completely background user.
+ * state, or is allowed to run as an expedited or user-initiated job,
+ * but is for a completely background user.
*/
- static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 4;
+ static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 5;
/**
* The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
* {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user,
* so can run as a background user job.
*/
- static final int WORK_TYPE_BGUSER = 1 << 5;
+ static final int WORK_TYPE_BGUSER = 1 << 6;
@VisibleForTesting
- static final int NUM_WORK_TYPES = 6;
+ static final int NUM_WORK_TYPES = 7;
private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1;
@IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
WORK_TYPE_NONE,
WORK_TYPE_TOP,
WORK_TYPE_FGS,
+ WORK_TYPE_UI,
WORK_TYPE_EJ,
WORK_TYPE_BG,
WORK_TYPE_BGUSER_IMPORTANT,
@@ -203,6 +207,8 @@
return "TOP";
case WORK_TYPE_FGS:
return "FGS";
+ case WORK_TYPE_UI:
+ return "UI";
case WORK_TYPE_EJ:
return "EJ";
case WORK_TYPE_BG:
@@ -238,8 +244,9 @@
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, .4f),
Pair.create(WORK_TYPE_FGS, .2f),
- Pair.create(WORK_TYPE_EJ, .2f), Pair.create(WORK_TYPE_BG, .1f),
- Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+ Pair.create(WORK_TYPE_UI, .1f),
+ Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .05f),
+ Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .5f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .25f),
@@ -250,6 +257,7 @@
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, .4f),
Pair.create(WORK_TYPE_FGS, .1f),
+ Pair.create(WORK_TYPE_UI, .1f),
Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .1f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
// defaultMax
@@ -260,8 +268,9 @@
new WorkTypeConfig("screen_on_low", DEFAULT_CONCURRENCY_LIMIT,
/* defaultMaxTotal */ DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
// defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, 2.0f / 3),
+ List.of(Pair.create(WORK_TYPE_TOP, .6f),
Pair.create(WORK_TYPE_FGS, .1f),
+ Pair.create(WORK_TYPE_UI, .1f),
Pair.create(WORK_TYPE_EJ, .1f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, 1.0f / 3),
@@ -271,9 +280,10 @@
new WorkTypeConfig("screen_on_critical", DEFAULT_CONCURRENCY_LIMIT,
/* defaultMaxTotal */ DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
// defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, 2.0f / 3),
+ List.of(Pair.create(WORK_TYPE_TOP, .7f),
Pair.create(WORK_TYPE_FGS, .1f),
- Pair.create(WORK_TYPE_EJ, .1f)),
+ Pair.create(WORK_TYPE_UI, .1f),
+ Pair.create(WORK_TYPE_EJ, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, 1.0f / 6),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1.0f / 6),
@@ -287,8 +297,9 @@
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, .3f),
Pair.create(WORK_TYPE_FGS, .2f),
- Pair.create(WORK_TYPE_EJ, .3f), Pair.create(WORK_TYPE_BG, .2f),
- Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+ Pair.create(WORK_TYPE_UI, .2f),
+ Pair.create(WORK_TYPE_EJ, .15f), Pair.create(WORK_TYPE_BG, .1f),
+ Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .6f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .2f),
@@ -299,8 +310,9 @@
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, .3f),
Pair.create(WORK_TYPE_FGS, .2f),
- Pair.create(WORK_TYPE_EJ, .3f), Pair.create(WORK_TYPE_BG, .2f),
- Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+ Pair.create(WORK_TYPE_UI, .2f),
+ Pair.create(WORK_TYPE_EJ, .15f), Pair.create(WORK_TYPE_BG, .1f),
+ Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .5f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -309,9 +321,11 @@
new WorkTypeConfig("screen_off_low", DEFAULT_CONCURRENCY_LIMIT,
/* defaultMaxTotal */ DEFAULT_CONCURRENCY_LIMIT * 6 / 10,
// defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, .4f),
- Pair.create(WORK_TYPE_FGS, .1f),
- Pair.create(WORK_TYPE_EJ, .2f), Pair.create(WORK_TYPE_BG, .1f)),
+ List.of(Pair.create(WORK_TYPE_TOP, .3f),
+ Pair.create(WORK_TYPE_FGS, .15f),
+ Pair.create(WORK_TYPE_UI, .15f),
+ Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .05f),
+ Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .25f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -320,9 +334,10 @@
new WorkTypeConfig("screen_off_critical", DEFAULT_CONCURRENCY_LIMIT,
/* defaultMaxTotal */ DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
// defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, .5f),
+ List.of(Pair.create(WORK_TYPE_TOP, .3f),
Pair.create(WORK_TYPE_FGS, .1f),
- Pair.create(WORK_TYPE_EJ, .1f)),
+ Pair.create(WORK_TYPE_UI, .1f),
+ Pair.create(WORK_TYPE_EJ, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .1f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -2097,10 +2112,12 @@
if (js.shouldTreatAsExpeditedJob()) {
classification |= WORK_TYPE_EJ;
+ } else if (js.shouldTreatAsUserInitiatedJob()) {
+ classification |= WORK_TYPE_UI;
}
} else {
if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE
- || js.shouldTreatAsExpeditedJob()) {
+ || js.shouldTreatAsExpeditedJob() || js.shouldTreatAsUserInitiatedJob()) {
classification |= WORK_TYPE_BGUSER_IMPORTANT;
}
// BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here.
@@ -2120,6 +2137,7 @@
static final String KEY_PREFIX_MAX_RATIO = KEY_PREFIX_MAX + "ratio_";
private static final String KEY_PREFIX_MAX_RATIO_TOP = KEY_PREFIX_MAX_RATIO + "top_";
private static final String KEY_PREFIX_MAX_RATIO_FGS = KEY_PREFIX_MAX_RATIO + "fgs_";
+ private static final String KEY_PREFIX_MAX_RATIO_UI = KEY_PREFIX_MAX_RATIO + "ui_";
private static final String KEY_PREFIX_MAX_RATIO_EJ = KEY_PREFIX_MAX_RATIO + "ej_";
private static final String KEY_PREFIX_MAX_RATIO_BG = KEY_PREFIX_MAX_RATIO + "bg_";
private static final String KEY_PREFIX_MAX_RATIO_BGUSER = KEY_PREFIX_MAX_RATIO + "bguser_";
@@ -2129,6 +2147,7 @@
static final String KEY_PREFIX_MIN_RATIO = KEY_PREFIX_MIN + "ratio_";
private static final String KEY_PREFIX_MIN_RATIO_TOP = KEY_PREFIX_MIN_RATIO + "top_";
private static final String KEY_PREFIX_MIN_RATIO_FGS = KEY_PREFIX_MIN_RATIO + "fgs_";
+ private static final String KEY_PREFIX_MIN_RATIO_UI = KEY_PREFIX_MIN_RATIO + "ui_";
private static final String KEY_PREFIX_MIN_RATIO_EJ = KEY_PREFIX_MIN_RATIO + "ej_";
private static final String KEY_PREFIX_MIN_RATIO_BG = KEY_PREFIX_MIN_RATIO + "bg_";
private static final String KEY_PREFIX_MIN_RATIO_BGUSER = KEY_PREFIX_MIN_RATIO + "bguser_";
@@ -2209,6 +2228,9 @@
final int maxFgs = getMaxValue(properties,
KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier, WORK_TYPE_FGS, oneIntBits);
mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs);
+ final int maxUi = getMaxValue(properties,
+ KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI, oneIntBits);
+ mMaxAllowedSlots.put(WORK_TYPE_UI, maxUi);
final int maxEj = getMaxValue(properties,
KEY_PREFIX_MAX_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ, oneIntBits);
mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj);
@@ -2237,6 +2259,12 @@
0, Math.min(maxFgs, remaining));
mMinReservedSlots.put(WORK_TYPE_FGS, minFgs);
remaining -= minFgs;
+ // Ensure ui is in the range [0, min(maxUi, remaining)]
+ final int minUi = getMinValue(properties,
+ KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI,
+ 0, Math.min(maxUi, remaining));
+ mMinReservedSlots.put(WORK_TYPE_UI, minUi);
+ remaining -= minUi;
// Ensure ej is in the range [0, min(maxEj, remaining)]
final int minEj = getMinValue(properties,
KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ,
@@ -2313,6 +2341,12 @@
pw.print(KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier,
mMaxAllowedSlots.get(WORK_TYPE_FGS))
.println();
+ pw.print(KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier,
+ mMinReservedSlots.get(WORK_TYPE_UI))
+ .println();
+ pw.print(KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier,
+ mMaxAllowedSlots.get(WORK_TYPE_UI))
+ .println();
pw.print(KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier,
mMinReservedSlots.get(WORK_TYPE_EJ))
.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
index f27da4a..06333f1 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
@@ -408,7 +408,7 @@
if (report.screenOffDurationMs > 0) {
pw.print(padStringWithSpaces(String.format("%d mAh (%.2f%%/hr)",
report.screenOffDischargeMah,
- 1.0 * report.screenOffDischargeMah * HOUR_IN_MILLIS
+ 100.0 * report.screenOffDischargeMah * HOUR_IN_MILLIS
/ (batteryCapacityMah * report.screenOffDurationMs)),
statColsLength));
} else {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 4001d9b..08c1a0c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -209,8 +209,10 @@
@GuardedBy("mLock")
private int mCurrentBatteryLevel;
- // TODO(250007395): make configurable per device
- private final int mTargetBackgroundBatteryLifeHours;
+ // TODO(250007395): make configurable per device (via config.xml)
+ private final int mDefaultTargetBackgroundBatteryLifeHours;
+ @GuardedBy("mLock")
+ private int mTargetBackgroundBatteryLifeHours;
private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
@Override
@@ -353,10 +355,11 @@
mConfigObserver = new ConfigObserver(mHandler, context);
- mTargetBackgroundBatteryLifeHours =
+ mDefaultTargetBackgroundBatteryLifeHours =
mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
- ? 200 // ~ 0.5%/hr
- : 100; // ~ 1%/hr
+ ? 100 // ~ 1.0%/hr
+ : 40; // ~ 2.5%/hr
+ mTargetBackgroundBatteryLifeHours = mDefaultTargetBackgroundBatteryLifeHours;
publishLocalService(EconomyManagerInternal.class, new LocalService());
}
@@ -1483,6 +1486,8 @@
private class ConfigObserver extends ContentObserver
implements DeviceConfig.OnPropertiesChangedListener {
private static final String KEY_ENABLE_TIP3 = "enable_tip3";
+ private static final String KEY_TARGET_BACKGROUND_BATTERY_LIFE_HOURS =
+ "target_bg_battery_life_hrs";
private static final boolean DEFAULT_ENABLE_TIP3 = true;
@@ -1541,6 +1546,13 @@
case KEY_ENABLE_TIP3:
ENABLE_TIP3 = properties.getBoolean(name, DEFAULT_ENABLE_TIP3);
break;
+ case KEY_TARGET_BACKGROUND_BATTERY_LIFE_HOURS:
+ synchronized (mLock) {
+ mTargetBackgroundBatteryLifeHours = properties.getInt(name,
+ mDefaultTargetBackgroundBatteryLifeHours);
+ maybeAdjustDesiredStockLevelLocked();
+ }
+ break;
default:
if (!economicPolicyUpdated
&& (name.startsWith("am") || name.startsWith("js")
@@ -1670,6 +1682,12 @@
pw.print("/");
pw.println(cakeToString(mScribe.getSatiatedConsumptionLimitLocked()));
+ pw.print("Target bg battery life (hours): ");
+ pw.print(mTargetBackgroundBatteryLifeHours);
+ pw.print(" (");
+ pw.print(String.format("%.2f", 100f / mTargetBackgroundBatteryLifeHours));
+ pw.println("%/hr)");
+
final long remainingConsumable = mScribe.getRemainingConsumableCakesLocked();
pw.print("Goods remaining: ");
pw.print(cakeToString(remainingConsumable));
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index b41c0d1..66327fd 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -717,8 +717,8 @@
out.startTag(null, XML_TAG_USER);
out.attributeInt(null, XML_ATTR_USER_ID, userId);
out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
- mRealtimeSinceUsersAddedOffsets.get(userId,
- mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime()));
+ mRealtimeSinceUsersAddedOffsets.get(userId, mLoadedTimeSinceFirstSetup)
+ + SystemClock.elapsedRealtime());
for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
final String pkgName = mLedgers.keyAt(uIdx, pIdx);
final Ledger ledger = mLedgers.get(userId, pkgName);
diff --git a/core/api/current.txt b/core/api/current.txt
index 5297bbd..3d76b66 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1488,6 +1488,7 @@
field public static final int strokeLineJoin = 16843788; // 0x101040c
field public static final int strokeMiterLimit = 16843789; // 0x101040d
field public static final int strokeWidth = 16843783; // 0x1010407
+ field public static final int stylusHandwritingSettingsActivity;
field public static final int subMenuArrow = 16844019; // 0x10104f3
field public static final int submitBackground = 16843912; // 0x1010488
field public static final int subtitle = 16843473; // 0x10102d1
@@ -7412,6 +7413,7 @@
method public void dump(android.util.Printer, String);
method public android.content.pm.ActivityInfo getActivityInfo();
method @NonNull public android.content.ComponentName getComponent();
+ method public int getHeadlessDeviceOwnerMode();
method public String getPackageName();
method public String getReceiverName();
method public String getTagForPolicy(int);
@@ -7423,6 +7425,8 @@
method public boolean usesPolicy(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminInfo> CREATOR;
+ field public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; // 0x1
+ field public static final int HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED = 0; // 0x0
field public static final int USES_ENCRYPTED_STORAGE = 7; // 0x7
field public static final int USES_POLICY_DISABLE_CAMERA = 8; // 0x8
field public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9; // 0x9
@@ -12877,16 +12881,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SigningInfo> CREATOR;
}
- public final class UserProperties implements android.os.Parcelable {
- method public int describeContents();
- method public int getShowInLauncher();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
- field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
- field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
- field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
- }
-
public final class VersionedPackage implements android.os.Parcelable {
ctor public VersionedPackage(@NonNull String, int);
ctor public VersionedPackage(@NonNull String, long);
@@ -13318,7 +13312,7 @@
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public android.os.Bundle getCredentialData();
method @NonNull public String getType();
- method public boolean requireSystemProvider();
+ method public boolean isSystemProviderRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
}
@@ -13343,8 +13337,8 @@
public final class CredentialManager {
method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
- method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
- method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
+ method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
+ method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
}
public class GetCredentialException extends java.lang.Exception {
@@ -13365,7 +13359,7 @@
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public android.os.Bundle getCredentialRetrievalData();
method @NonNull public String getType();
- method public boolean requireSystemProvider();
+ method public boolean isSystemProviderRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
}
@@ -19278,10 +19272,20 @@
package android.hardware.input {
+ public final class HostUsiVersion implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getMajorVersion();
+ method public int getMinorVersion();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.HostUsiVersion> CREATOR;
+ }
+
public final class InputManager {
+ method @Nullable public android.hardware.input.HostUsiVersion getHostUsiVersion(@NonNull android.view.Display);
method @Nullable public android.view.InputDevice getInputDevice(int);
method public int[] getInputDeviceIds();
method @FloatRange(from=0, to=1) public float getMaximumObscuringOpacityForTouch();
+ method public boolean isStylusPointerIconEnabled();
method public void registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler);
method public void unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener);
method @Nullable public android.view.VerifiedInputEvent verifyInputEvent(@NonNull android.view.InputEvent);
@@ -24051,6 +24055,7 @@
method @Nullable public android.os.Bundle getControlHints();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getDeselectableRoutes();
method @NonNull public String getId();
+ method @NonNull public android.media.RoutingSessionInfo getRoutingSessionInfo();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes();
method public int getVolume();
@@ -28400,15 +28405,24 @@
public final class NfcAdapter {
method public void disableForegroundDispatch(android.app.Activity);
+ method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
method public void disableReaderMode(android.app.Activity);
method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
+ method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
+ method @Deprecated public boolean invokeBeam(android.app.Activity);
method public boolean isEnabled();
+ method @Deprecated public boolean isNdefPushEnabled();
method public boolean isSecureNfcEnabled();
method public boolean isSecureNfcSupported();
+ method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
+ method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
+ method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+ method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+ method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -28440,6 +28454,18 @@
field public static final int STATE_TURNING_ON = 2; // 0x2
}
+ @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
+ method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
+ }
+
+ @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
+ method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
+ }
+
+ @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
+ method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
+ }
+
public static interface NfcAdapter.OnTagRemovedListener {
method public void onTagRemoved();
}
@@ -33189,7 +33215,6 @@
method public android.os.UserHandle getUserForSerialNumber(long);
method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS", "android.permission.QUERY_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
- method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean hasUserRestriction(String);
@@ -35340,7 +35365,7 @@
}
public static final class ContactsContract.CommonDataKinds.Email implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, CharSequence);
+ method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
method public static int getTypeLabelResource(int);
field public static final String ADDRESS = "data1";
field public static final android.net.Uri CONTENT_FILTER_URI;
@@ -35361,7 +35386,7 @@
}
public static final class ContactsContract.CommonDataKinds.Event implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, CharSequence);
+ method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
method public static int getTypeResource(Integer);
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_event";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
@@ -35394,7 +35419,7 @@
public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
method public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
method public static int getProtocolLabelResource(int);
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, CharSequence);
+ method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
method public static int getTypeLabelResource(int);
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
field public static final String CUSTOM_PROTOCOL = "data6";
@@ -35440,7 +35465,7 @@
}
public static final class ContactsContract.CommonDataKinds.Organization implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, CharSequence);
+ method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
method public static int getTypeLabelResource(int);
field public static final String COMPANY = "data1";
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/organization";
@@ -35459,7 +35484,7 @@
}
public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, CharSequence);
+ method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
method public static int getTypeLabelResource(int);
field public static final android.net.Uri CONTENT_FILTER_URI;
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/phone_v2";
@@ -35506,7 +35531,7 @@
}
public static final class ContactsContract.CommonDataKinds.Relation implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, CharSequence);
+ method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
method public static int getTypeLabelResource(int);
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/relation";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
@@ -35530,7 +35555,7 @@
}
public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, CharSequence);
+ method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
method public static int getTypeLabelResource(int);
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
@@ -35561,7 +35586,7 @@
}
public static final class ContactsContract.CommonDataKinds.StructuredPostal implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, CharSequence);
+ method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
method public static int getTypeLabelResource(int);
field public static final String CITY = "data7";
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/postal-address_v2";
@@ -40570,7 +40595,6 @@
method public void setDisabledShowContext(int);
method public final void setUiHints(@NonNull android.os.Bundle);
method public void showSession(android.os.Bundle, int);
- field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final String SERVICE_META_DATA = "android.voice_interaction";
}
@@ -40631,6 +40655,7 @@
method public void startAssistantActivity(android.content.Intent);
method public void startVoiceActivity(android.content.Intent);
method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
+ field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
@@ -41369,8 +41394,7 @@
field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
}
- public final class CallControl implements java.lang.AutoCloseable {
- method public void close();
+ public final class CallControl {
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
@@ -44511,6 +44535,7 @@
field public static final int RESULT_SMS_SEND_RETRY_FAILED = 30; // 0x1e
field public static final int RESULT_SYSTEM_ERROR = 15; // 0xf
field public static final int RESULT_UNEXPECTED_EVENT_STOP_SENDING = 28; // 0x1c
+ field public static final int RESULT_USER_NOT_ALLOWED = 33; // 0x21
field public static final int SMS_RP_CAUSE_CALL_BARRING = 10; // 0xa
field public static final int SMS_RP_CAUSE_CONGESTION = 42; // 0x2a
field public static final int SMS_RP_CAUSE_DESTINATION_OUT_OF_ORDER = 27; // 0x1b
@@ -44872,6 +44897,7 @@
method public int getCardIdForDefaultEuicc();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig();
method public int getCarrierIdFromSimMccMnc();
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void getCarrierRestrictionStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.telephony.CellLocation getCellLocation();
method public int getDataActivity();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public int getDataNetworkType();
@@ -45031,6 +45057,10 @@
field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
field public static final int CALL_STATE_RINGING = 1; // 0x1
field public static final String CAPABILITY_SLICING_CONFIG_SUPPORTED = "CAPABILITY_SLICING_CONFIG_SUPPORTED";
+ field public static final int CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED = 1; // 0x1
+ field public static final int CARRIER_RESTRICTION_STATUS_RESTRICTED = 2; // 0x2
+ field public static final int CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER = 3; // 0x3
+ field public static final int CARRIER_RESTRICTION_STATUS_UNKNOWN = 0; // 0x0
field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
@@ -50803,6 +50833,7 @@
field public static final int TYPE_GRAB = 1020; // 0x3fc
field public static final int TYPE_GRABBING = 1021; // 0x3fd
field public static final int TYPE_HAND = 1002; // 0x3ea
+ field public static final int TYPE_HANDWRITING = 1022; // 0x3fe
field public static final int TYPE_HELP = 1003; // 0x3eb
field public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
field public static final int TYPE_NO_DROP = 1012; // 0x3f4
@@ -51021,6 +51052,7 @@
method public void relayout(int, int);
method public void release();
method public void setView(@NonNull android.view.View, int, int);
+ method public boolean transferTouchGestureToHost();
}
public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
@@ -52920,10 +52952,10 @@
method public void onRestrictedCaptionAreaChanged(android.graphics.Rect);
}
- public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.view.WindowAnimationFrameStats> CREATOR;
+ @Deprecated public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.view.WindowAnimationFrameStats> CREATOR;
}
public final class WindowContentFrameStats extends android.view.FrameStats implements android.os.Parcelable {
@@ -54886,6 +54918,7 @@
public final class InputMethodInfo implements android.os.Parcelable {
ctor public InputMethodInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
ctor public InputMethodInfo(String, String, CharSequence, String);
+ method @Nullable public android.content.Intent createStylusHandwritingSettingsActivityIntent();
method public int describeContents();
method public void dump(android.util.Printer, String);
method public android.content.ComponentName getComponent();
@@ -54904,6 +54937,7 @@
method public boolean supportsStylusHandwriting();
method public boolean suppressesSpellChecker();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final String ACTION_STYLUS_HANDWRITING_SETTINGS = "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS";
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
}
@@ -55038,6 +55072,22 @@
method @NonNull public android.view.inputmethod.InsertGesture.Builder setTextToInsert(@NonNull String);
}
+ public final class InsertModeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.os.CancellationSignal getCancellationSignal();
+ method @NonNull public android.graphics.PointF getInsertionPoint();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InsertModeGesture> CREATOR;
+ }
+
+ public static final class InsertModeGesture.Builder {
+ ctor public InsertModeGesture.Builder();
+ method @NonNull public android.view.inputmethod.InsertModeGesture build();
+ method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setCancellationSignal(@NonNull android.os.CancellationSignal);
+ method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setFallbackText(@Nullable String);
+ method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setInsertionPoint(@NonNull android.graphics.PointF);
+ }
+
public final class JoinOrSplitGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.graphics.PointF getJoinOrSplitPoint();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 55ef6de..af35d96 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -83,7 +83,7 @@
public abstract class Context {
method @NonNull public android.content.Context createContextForSdkInSandbox(@NonNull android.content.pm.ApplicationInfo, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method @NonNull public android.os.IBinder getIApplicationThreadBinder();
+ method @NonNull public android.os.IBinder getProcessToken();
method @NonNull public android.os.UserHandle getUser();
field public static final String PAC_PROXY_SERVICE = "pac_proxy";
field public static final String TEST_NETWORK_SERVICE = "test_network";
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 5c4fd10..1fa1e89 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -252,34 +252,6 @@
}
-package android.nfc {
-
- public final class NfcAdapter {
- method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
- method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
- method @Deprecated public boolean invokeBeam(android.app.Activity);
- method @Deprecated public boolean isNdefPushEnabled();
- method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
- method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
- method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
- method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
- method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
- }
-
- @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
- method public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
- }
-
- @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
- method public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
- }
-
- @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
- method public void onNdefPushComplete(android.nfc.NfcEvent);
- }
-
-}
-
package android.os {
public class BatteryManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bb7d0c8..d99e151 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1278,6 +1278,7 @@
field public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
field public static final int STATUS_HAS_DEVICE_OWNER = 1; // 0x1
field public static final int STATUS_HAS_PAIRED = 8; // 0x8
+ field public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16; // 0x10
field public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
field public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; // 0x5
field public static final int STATUS_NOT_SYSTEM_USER = 7; // 0x7
@@ -3010,8 +3011,13 @@
method public void onIntentIntercepted(@NonNull android.content.Intent);
}
+ public static interface VirtualDeviceManager.SoundEffectListener {
+ method public void onPlaySoundEffect(int);
+ }
+
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+ method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
@@ -3030,6 +3036,7 @@
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+ method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
}
@@ -3263,6 +3270,7 @@
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
field public static final String TETHERING_SERVICE = "tethering";
+ field public static final String TIME_MANAGER_SERVICE = "time_manager";
field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
field public static final String UWB_SERVICE = "uwb";
@@ -3900,6 +3908,14 @@
method @NonNull public android.content.pm.SuspendDialogInfo.Builder setTitle(@NonNull String);
}
+ public final class UserProperties implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean getIsCredentialSharableWithParent();
+ method public boolean getIsMediaSharedWithParent();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
+ }
+
}
package android.content.pm.dex {
@@ -9686,7 +9702,9 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
@@ -9695,8 +9713,10 @@
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
+ method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
@@ -10403,6 +10423,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public long[] getSerialNumbersOfUsers(boolean);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int getUserSwitchability();
@@ -10411,11 +10432,11 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser();
method public boolean isCloneProfile();
- method public boolean isCredentialSharableWithParent();
+ method @Deprecated public boolean isCredentialSharableWithParent();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isMainUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
- method public boolean isMediaSharedWithParent();
+ method @Deprecated public boolean isMediaSharedWithParent();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
method public static boolean isRemoveResultSuccessful(int);
method public boolean isRestrictedProfile();
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 1c10356..2c5acf1 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -140,17 +140,6 @@
}
-package android.nfc {
-
- public final class NfcAdapter {
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
- method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
- field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
- }
-
-}
-
package android.os {
public class Build {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8c64e40..8dc7dc3 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -759,6 +759,7 @@
method public int getUserId();
method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+ method public void updateDeviceId(int);
field public static final String ATTENTION_SERVICE = "attention";
field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
@@ -908,8 +909,6 @@
method public boolean isProfile();
method public boolean isQuietModeEnabled();
method public boolean isRestricted();
- method public boolean isSystemOnly();
- method public static boolean isSystemOnly(int);
method public boolean supportsSwitchTo();
method public boolean supportsSwitchToByUser();
method public void writeToParcel(android.os.Parcel, int);
@@ -947,6 +946,13 @@
field public String userType;
}
+ public final class UserProperties implements android.os.Parcelable {
+ method public int getShowInLauncher();
+ field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
+ field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+ field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
+ }
+
}
package android.content.res {
@@ -976,6 +982,59 @@
}
+package android.credentials {
+
+ public final class CredentialDescription implements android.os.Parcelable {
+ ctor public CredentialDescription(@NonNull String, @NonNull String, @NonNull java.util.List<android.service.credentials.CredentialEntry>);
+ method public int describeContents();
+ method @NonNull public java.util.List<android.service.credentials.CredentialEntry> getCredentialEntries();
+ method @NonNull public String getFlattenedRequestString();
+ method @NonNull public String getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialDescription> CREATOR;
+ }
+
+ public final class CredentialManager {
+ method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.RegisterCredentialDescriptionException>);
+ method public void unRegisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.UnregisterCredentialDescriptionException>);
+ }
+
+ public class RegisterCredentialDescriptionException extends java.lang.Exception {
+ ctor public RegisterCredentialDescriptionException(@NonNull String, @Nullable String);
+ ctor public RegisterCredentialDescriptionException(@NonNull String, @Nullable String, @Nullable Throwable);
+ ctor public RegisterCredentialDescriptionException(@NonNull String, @Nullable Throwable);
+ ctor public RegisterCredentialDescriptionException(@NonNull String);
+ field @NonNull public final String errorType;
+ }
+
+ public final class RegisterCredentialDescriptionRequest implements android.os.Parcelable {
+ ctor public RegisterCredentialDescriptionRequest(@NonNull android.credentials.CredentialDescription);
+ ctor public RegisterCredentialDescriptionRequest(@NonNull java.util.List<android.credentials.CredentialDescription>);
+ method public int describeContents();
+ method @NonNull public java.util.List<android.credentials.CredentialDescription> getCredentialDescriptions();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.RegisterCredentialDescriptionRequest> CREATOR;
+ field public static final String FLATTENED_REQUEST_STRING_KEY = "flattened_request_string";
+ }
+
+ public class UnregisterCredentialDescriptionException extends java.lang.Exception {
+ ctor public UnregisterCredentialDescriptionException(@NonNull String, @Nullable String);
+ ctor public UnregisterCredentialDescriptionException(@NonNull String, @Nullable String, @Nullable Throwable);
+ ctor public UnregisterCredentialDescriptionException(@NonNull String, @Nullable Throwable);
+ ctor public UnregisterCredentialDescriptionException(@NonNull String);
+ field @NonNull public final String errorType;
+ }
+
+ public final class UnregisterCredentialDescriptionRequest implements android.os.Parcelable {
+ ctor public UnregisterCredentialDescriptionRequest(@NonNull android.credentials.CredentialDescription);
+ method public int describeContents();
+ method @NonNull public android.credentials.CredentialDescription getCredentialDescription();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.UnregisterCredentialDescriptionRequest> CREATOR;
+ }
+
+}
+
package android.credentials.ui {
public final class CreateCredentialProviderData extends android.credentials.ui.ProviderData implements android.os.Parcelable {
@@ -1416,6 +1475,8 @@
package android.hardware.location {
public final class ContextHubManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public boolean disableTestMode();
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public boolean enableTestMode();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public long[] getPreloadedNanoAppIds(@NonNull android.hardware.location.ContextHubInfo);
}
@@ -1629,20 +1690,27 @@
public class AudioManager {
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void forceComputeCsdOnAllDevices(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void forceUseFrameworkMel(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public float getCsd();
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
method public static final int[] getPublicStreamTypes();
method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public float getRs2Value();
method public int getStreamMinVolumeInt(int);
method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
method public boolean hasRegisteredDynamicPolicy();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public boolean isCsdEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setCsd(float);
method public void setRampingRingerEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setRs2Value(float);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
}
@@ -2032,7 +2100,6 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
- method public static boolean isSplitSystemUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
method public boolean isVisibleBackgroundUsersSupported();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
@@ -2564,6 +2631,7 @@
public abstract class DreamOverlayService extends android.app.Service {
ctor public DreamOverlayService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onEndDream();
method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
method public final void requestExit();
method public final boolean shouldShowComplications();
@@ -2796,6 +2864,7 @@
field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
field public static final int HAL_SERVICE_MODEM = 3; // 0x3
field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
+ field public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
field public static final int HAL_SERVICE_SIM = 5; // 0x5
field public static final int HAL_SERVICE_VOICE = 6; // 0x6
field public static final android.util.Pair HAL_VERSION_UNKNOWN;
@@ -3338,6 +3407,7 @@
field public static final int GESTURE_TYPE_DELETE = 4; // 0x4
field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40
field public static final int GESTURE_TYPE_INSERT = 2; // 0x2
+ field public static final int GESTURE_TYPE_INSERT_MODE = 128; // 0x80
field public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 16; // 0x10
field public static final int GESTURE_TYPE_NONE = 0; // 0x0
field public static final int GESTURE_TYPE_REMOVE_SPACE = 8; // 0x8
@@ -3358,6 +3428,7 @@
}
public final class InputMethodInfo implements android.os.Parcelable {
+ ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String);
ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a9d14df..a81ef18 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -23,6 +23,7 @@
import android.content.pm.ActivityInfo.Config;
import android.content.res.ConstantState;
import android.os.Build;
+import android.util.LongArray;
import java.util.ArrayList;
@@ -546,7 +547,6 @@
*/
void skipToEndValue(boolean inReverse) {}
-
/**
* Internal use only.
*
@@ -559,9 +559,36 @@
}
/**
- * Internal use only.
+ * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
+ * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
+ * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
+ * done between the two times.
*/
- void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
+
+ /**
+ * Internal use only. This animates any animation that has ended since lastPlayTime.
+ * If an animation hasn't been finished, no change will be made.
+ */
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
+
+ /**
+ * Internal use only. Adds all start times (after delay) to and end times to times.
+ * The value must include offset.
+ */
+ void getStartAndEndTimes(LongArray times, long offset) {
+ long startTime = offset + getStartDelay();
+ if (times.indexOf(startTime) < 0) {
+ times.add(startTime);
+ }
+ long duration = getTotalDuration();
+ if (duration != DURATION_INFINITE) {
+ long endTime = duration + offset;
+ if (times.indexOf(endTime) < 0) {
+ times.add(endTime);
+ }
+ }
+ }
/**
* <p>An animation listener receives notifications from an animation.
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index bc8db02..257adfe 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -23,9 +23,11 @@
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.LongArray;
import android.view.animation.Animation;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
@@ -181,6 +183,16 @@
*/
private long mPauseTime = -1;
+ /**
+ * The start and stop times of all descendant animators.
+ */
+ private long[] mChildStartAndStopTimes;
+
+ /**
+ * Tracks whether we've notified listeners of the onAnimationStart() event.
+ */
+ private boolean mStartListenersCalled;
+
// This is to work around a bug in b/34736819. This needs to be removed once app team
// fixes their side.
private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -729,14 +741,7 @@
startAnimation();
}
- if (mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this, inReverse);
- }
- }
+ notifyStartListeners(inReverse);
if (isEmptySet) {
// In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
// onAnimationEnd() right away.
@@ -744,6 +749,32 @@
}
}
+ private void notifyStartListeners(boolean inReverse) {
+ if (mListeners != null && !mStartListenersCalled) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ AnimatorListener listener = tmpListeners.get(i);
+ listener.onAnimationStart(this, inReverse);
+ }
+ }
+ mStartListenersCalled = true;
+ }
+
+ private void notifyEndListeners(boolean inReverse) {
+ if (mListeners != null && mStartListenersCalled) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ AnimatorListener listener = tmpListeners.get(i);
+ listener.onAnimationEnd(this, inReverse);
+ }
+ }
+ mStartListenersCalled = false;
+ }
+
// Returns true if set is empty or contains nothing but animator sets with no start delay.
private static boolean isEmptySet(AnimatorSet set) {
if (set.getStartDelay() > 0) {
@@ -779,26 +810,25 @@
@Override
void skipToEndValue(boolean inReverse) {
- if (!isInitialized()) {
- throw new UnsupportedOperationException("Children must be initialized.");
- }
-
// This makes sure the animation events are sorted an up to date.
initAnimation();
+ initChildren();
// Calling skip to the end in the sequence that they would be called in a forward/reverse
// run, such that the sequential animations modifying the same property would have
// the right value in the end.
if (inReverse) {
for (int i = mEvents.size() - 1; i >= 0; i--) {
- if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ event.mNode.mAnimation.skipToEndValue(true);
}
}
} else {
for (int i = 0; i < mEvents.size(); i++) {
- if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
- mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ event.mNode.mAnimation.skipToEndValue(false);
}
}
}
@@ -814,72 +844,216 @@
* {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
* as needed, based on the last play time and current play time.
*/
- @Override
- void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
- if (currentPlayTime < 0 || lastPlayTime < 0) {
+ private void animateBasedOnPlayTime(
+ long currentPlayTime,
+ long lastPlayTime,
+ boolean inReverse,
+ boolean notify
+ ) {
+ if (currentPlayTime < 0 || lastPlayTime < -1) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
}
// TODO: take into account repeat counts and repeat callback when repeat is implemented.
- // Clamp currentPlayTime and lastPlayTime
- // TODO: Make this more efficient
-
- // Convert the play times to the forward direction.
if (inReverse) {
- if (getTotalDuration() == DURATION_INFINITE) {
- throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
- + " duration");
+ long duration = getTotalDuration();
+ if (duration == DURATION_INFINITE) {
+ throw new UnsupportedOperationException(
+ "Cannot reverse AnimatorSet with infinite duration"
+ );
}
- long duration = getTotalDuration() - mStartDelay;
+ // Convert the play times to the forward direction.
currentPlayTime = Math.min(currentPlayTime, duration);
currentPlayTime = duration - currentPlayTime;
lastPlayTime = duration - lastPlayTime;
- inReverse = false;
}
- ArrayList<Node> unfinishedNodes = new ArrayList<>();
- // Assumes forward playing from here on.
- for (int i = 0; i < mEvents.size(); i++) {
- AnimationEvent event = mEvents.get(i);
- if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
- break;
- }
+ long[] startEndTimes = ensureChildStartAndEndTimes();
+ int index = findNextIndex(lastPlayTime, startEndTimes);
+ int endIndex = findNextIndex(currentPlayTime, startEndTimes);
- // This animation started prior to the current play time, and won't finish before the
- // play time, add to the unfinished list.
- if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- if (event.mNode.mEndTime == DURATION_INFINITE
- || event.mNode.mEndTime > currentPlayTime) {
- unfinishedNodes.add(event.mNode);
+ // Change values at the start/end times so that values are set in the right order.
+ // We don't want an animator that would finish before another to override the value
+ // set by another animator that finishes earlier.
+ if (currentPlayTime >= lastPlayTime) {
+ while (index < endIndex) {
+ long playTime = startEndTimes[index];
+ if (lastPlayTime != playTime) {
+ animateSkipToEnds(playTime, lastPlayTime, notify);
+ animateValuesInRange(playTime, lastPlayTime, notify);
+ lastPlayTime = playTime;
+ }
+ index++;
+ }
+ } else {
+ while (index > endIndex) {
+ index--;
+ long playTime = startEndTimes[index];
+ if (lastPlayTime != playTime) {
+ animateSkipToEnds(playTime, lastPlayTime, notify);
+ animateValuesInRange(playTime, lastPlayTime, notify);
+ lastPlayTime = playTime;
}
}
- // For animations that do finish before the play time, end them in the sequence that
- // they would in a normal run.
- if (event.mEvent == AnimationEvent.ANIMATION_END) {
- // Skip to the end of the animation.
- event.mNode.mAnimation.skipToEndValue(false);
+ }
+ if (currentPlayTime != lastPlayTime) {
+ animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
+ animateValuesInRange(currentPlayTime, lastPlayTime, notify);
+ }
+ }
+
+ /**
+ * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
+ * is returned. Otherwise, it returns the index at which it would be placed if it were
+ * to be inserted.
+ */
+ private int findNextIndex(long playTime, long[] startEndTimes) {
+ int index = Arrays.binarySearch(startEndTimes, playTime);
+ if (index < 0) {
+ index = -index - 1;
+ } else {
+ index++;
+ }
+ return index;
+ }
+
+ @Override
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ initAnimation();
+
+ if (lastPlayTime > currentPlayTime) {
+ if (notify) {
+ notifyStartListeners(true);
+ }
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_END
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if (currentPlayTime <= start && start < lastPlayTime) {
+ animator.animateSkipToEnds(
+ 0,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ } else if (start <= currentPlayTime && currentPlayTime <= end) {
+ animator.animateSkipToEnds(
+ currentPlayTime - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ }
+ }
+ }
+ if (currentPlayTime <= 0 && notify) {
+ notifyEndListeners(true);
+ }
+ } else {
+ if (notify) {
+ notifyStartListeners(false);
+ }
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if (lastPlayTime < end && end <= currentPlayTime) {
+ animator.animateSkipToEnds(
+ end - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ } else if (start <= currentPlayTime && currentPlayTime <= end) {
+ animator.animateSkipToEnds(
+ currentPlayTime - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ }
+ }
+ }
+ if (currentPlayTime >= getTotalDuration() && notify) {
+ notifyEndListeners(false);
+ }
+ }
+ }
+
+ @Override
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ initAnimation();
+
+ if (notify) {
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else {
+ long duration = getTotalDuration();
+ if (duration >= 0
+ && (lastPlayTime > duration || (lastPlayTime == duration
+ && currentPlayTime < duration))
+ ) {
+ notifyStartListeners(true);
+ }
}
}
- // Seek unfinished animation to the right time.
- for (int i = 0; i < unfinishedNodes.size(); i++) {
- Node node = unfinishedNodes.get(i);
- long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
- if (!inReverse) {
- playTime -= node.mAnimation.getStartDelay();
- }
- node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
- }
-
- // Seek not yet started animations.
- for (int i = 0; i < mEvents.size(); i++) {
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
AnimationEvent event = mEvents.get(i);
- if (event.getTime() > currentPlayTime
- && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- event.mNode.mAnimation.skipToEndValue(true);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if ((start < currentPlayTime && currentPlayTime < end)
+ || (start == currentPlayTime && lastPlayTime < start)
+ || (end == currentPlayTime && lastPlayTime > end)
+ ) {
+ animator.animateValuesInRange(
+ currentPlayTime - node.mStartTime,
+ Math.max(-1, lastPlayTime - node.mStartTime),
+ notify
+ );
+ }
}
}
+ }
+ private long[] ensureChildStartAndEndTimes() {
+ if (mChildStartAndStopTimes == null) {
+ LongArray startAndEndTimes = new LongArray();
+ getStartAndEndTimes(startAndEndTimes, 0);
+ long[] times = startAndEndTimes.toArray();
+ Arrays.sort(times);
+ mChildStartAndStopTimes = times;
+ }
+ return mChildStartAndStopTimes;
+ }
+
+ @Override
+ void getStartAndEndTimes(LongArray times, long offset) {
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && event.mNode.mStartTime != DURATION_INFINITE
+ ) {
+ event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
+ }
+ }
}
@Override
@@ -899,10 +1073,6 @@
return mChildrenInitialized;
}
- private void skipToStartValue(boolean inReverse) {
- skipToEndValue(!inReverse);
- }
-
/**
* Sets the position of the animation to the specified point in time. This time should
* be between 0 and the total duration of the animation, including any repetition. If
@@ -910,6 +1080,11 @@
* set to this time; it will simply set the time to this value and perform any appropriate
* actions based on that time. If the animation is already running, then setCurrentPlayTime()
* will set the current playing time to this value and continue playing from that point.
+ * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
+ * that hasn't been {@link #start()}ed, will issue
+ * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
+ * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
+ * events.
*
* @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
* Unless the animation is reversing, the playtime is considered the time since
@@ -926,29 +1101,27 @@
if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
|| playTime < 0) {
throw new UnsupportedOperationException("Error: Play time should always be in between"
- + "0 and duration.");
+ + " 0 and duration.");
}
initAnimation();
+ long lastPlayTime = mSeekState.getPlayTime();
if (!isStarted() || isPaused()) {
- if (mReversing) {
+ if (mReversing && !isStarted()) {
throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
+ " should not be set when AnimatorSet is not started.");
}
if (!mSeekState.isActive()) {
findLatestEventIdForTime(0);
- // Set all the values to start values.
initChildren();
+ // Set all the values to start values.
+ skipToEndValue(!mReversing);
mSeekState.setPlayTime(0, mReversing);
}
- animateBasedOnPlayTime(playTime, 0, mReversing);
- mSeekState.setPlayTime(playTime, mReversing);
- } else {
- // If the animation is running, just set the seek time and wait until the next frame
- // (i.e. doAnimationFrame(...)) to advance the animation.
- mSeekState.setPlayTime(playTime, mReversing);
}
+ animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
+ mSeekState.setPlayTime(playTime, mReversing);
}
/**
@@ -981,10 +1154,16 @@
private void initChildren() {
if (!isInitialized()) {
mChildrenInitialized = true;
- // Forcefully initialize all children based on their end time, so that if the start
- // value of a child is dependent on a previous animation, the animation will be
- // initialized after the the previous animations have been advanced to the end.
- skipToEndValue(false);
+
+ // We have to initialize all the start values so that they are based on the previous
+ // values.
+ long[] times = ensureChildStartAndEndTimes();
+
+ long previousTime = -1;
+ for (long time : times) {
+ animateBasedOnPlayTime(time, previousTime, false, false);
+ previousTime = time;
+ }
}
}
@@ -1058,7 +1237,7 @@
for (int i = 0; i < mPlayingSet.size(); i++) {
Node node = mPlayingSet.get(i);
if (!node.mEnded) {
- pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
}
}
@@ -1129,7 +1308,7 @@
pulseFrame(node, 0);
} else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
// end event:
- pulseFrame(node, getPlayTimeForNode(playTime, node));
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
}
}
} else {
@@ -1150,7 +1329,7 @@
pulseFrame(node, 0);
} else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
// start event:
- pulseFrame(node, getPlayTimeForNode(playTime, node));
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
}
}
}
@@ -1172,11 +1351,15 @@
}
}
- private long getPlayTimeForNode(long overallPlayTime, Node node) {
- return getPlayTimeForNode(overallPlayTime, node, mReversing);
+ private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
+ return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
}
- private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
+ private long getPlayTimeForNodeIncludingDelay(
+ long overallPlayTime,
+ Node node,
+ boolean inReverse
+ ) {
if (inReverse) {
overallPlayTime = getTotalDuration() - overallPlayTime;
return node.mEndTime - overallPlayTime;
@@ -1198,26 +1381,8 @@
}
// Set the child animators to the right end:
if (mShouldResetValuesAtStart) {
- if (isInitialized()) {
- skipToEndValue(!mReversing);
- } else if (mReversing) {
- // Reversing but haven't initialized all the children yet.
- initChildren();
- skipToEndValue(!mReversing);
- } else {
- // If not all children are initialized and play direction is forward
- for (int i = mEvents.size() - 1; i >= 0; i--) {
- if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- Animator anim = mEvents.get(i).mNode.mAnimation;
- // Only reset the animations that have been initialized to start value,
- // so that if they are defined without a start value, they will get the
- // values set at the right time (i.e. the next animation run)
- if (anim.isInitialized()) {
- anim.skipToEndValue(true);
- }
- }
- }
- }
+ initChildren();
+ skipToEndValue(!mReversing);
}
if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
@@ -1292,15 +1457,7 @@
// No longer receive callbacks
removeAnimationCallback();
- // Call end listener
- if (mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this, mReversing);
- }
- }
+ notifyEndListeners(mReversing);
removeAnimationEndListener();
mSelfPulse = true;
mReversing = false;
@@ -1922,11 +2079,11 @@
}
void setPlayTime(long playTime, boolean inReverse) {
- // TODO: This can be simplified.
-
// Clamp the play time
if (getTotalDuration() != DURATION_INFINITE) {
mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+ } else {
+ mPlayTime = playTime;
}
mPlayTime = Math.max(0, mPlayTime);
mSeekingInReverse = inReverse;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 6ab7ae6..7009725 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -324,8 +324,9 @@
listenerCopy = new ArrayList<>(sDurationScaleChangeListeners);
}
- for (WeakReference<DurationScaleChangeListener> listenerRef : listenerCopy) {
- final DurationScaleChangeListener listener = listenerRef.get();
+ int listenersSize = listenerCopy.size();
+ for (int i = 0; i < listenersSize; i++) {
+ final DurationScaleChangeListener listener = listenerCopy.get(i).get();
if (listener != null) {
listener.onChanged(durationScale);
}
@@ -624,7 +625,7 @@
public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
- mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+ mValuesMap = new HashMap<>(numValues);
for (int i = 0; i < numValues; ++i) {
PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
@@ -658,9 +659,11 @@
@CallSuper
void initAnimation() {
if (!mInitialized) {
- int numValues = mValues.length;
- for (int i = 0; i < numValues; ++i) {
- mValues[i].init();
+ if (mValues != null) {
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].init();
+ }
}
mInitialized = true;
}
@@ -1105,18 +1108,30 @@
}
}
- private void notifyStartListeners() {
+ private void notifyStartListeners(boolean isReversing) {
if (mListeners != null && !mStartListenersCalled) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this, mReversing);
+ tmpListeners.get(i).onAnimationStart(this, isReversing);
}
}
mStartListenersCalled = true;
}
+ private void notifyEndListeners(boolean isReversing) {
+ if (mListeners != null && mStartListenersCalled) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onAnimationEnd(this, isReversing);
+ }
+ }
+ mStartListenersCalled = false;
+ }
+
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
@@ -1207,12 +1222,16 @@
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
- notifyStartListeners();
+ notifyStartListeners(mReversing);
}
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- for (AnimatorListener listener : tmpListeners) {
- listener.onAnimationCancel(this);
+ int listenersSize = mListeners.size();
+ if (listenersSize > 0) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ for (int i = 0; i < listenersSize; i++) {
+ AnimatorListener listener = tmpListeners.get(i);
+ listener.onAnimationCancel(this);
+ }
}
}
endAnimation();
@@ -1317,22 +1336,14 @@
boolean notify = (mStarted || mRunning) && mListeners != null;
if (notify && !mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
- notifyStartListeners();
+ notifyStartListeners(mReversing);
}
mRunning = false;
mStarted = false;
- mStartListenersCalled = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
- if (notify && mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this, mReversing);
- }
- }
+ notifyEndListeners(mReversing);
// mReversing needs to be reset *after* notifying the listeners for the end callbacks.
mReversing = false;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -1359,9 +1370,8 @@
} else {
mOverallFraction = 0f;
}
- if (mListeners != null) {
- notifyStartListeners();
- }
+
+ notifyStartListeners(mReversing);
}
/**
@@ -1452,16 +1462,32 @@
* will be called.
*/
@Override
- void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
- if (currentPlayTime < 0 || lastPlayTime < 0) {
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ if (currentPlayTime < 0 || lastPlayTime < -1) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
}
initAnimation();
+ long duration = getTotalDuration();
+ if (notify) {
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else if (lastPlayTime > duration
+ || (lastPlayTime == duration && currentPlayTime < duration)
+ ) {
+ notifyStartListeners(true);
+ }
+ }
+ if (duration >= 0) {
+ lastPlayTime = Math.min(duration, lastPlayTime);
+ }
+ lastPlayTime -= mStartDelay;
+ currentPlayTime -= mStartDelay;
+
// Check whether repeat callback is needed only when repeat count is non-zero
if (mRepeatCount > 0) {
- int iteration = (int) (currentPlayTime / mDuration);
- int lastIteration = (int) (lastPlayTime / mDuration);
+ int iteration = Math.max(0, (int) (currentPlayTime / mDuration));
+ int lastIteration = Math.max(0, (int) (lastPlayTime / mDuration));
// Clamp iteration to [0, mRepeatCount]
iteration = Math.min(iteration, mRepeatCount);
@@ -1477,16 +1503,37 @@
}
}
- if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
- skipToEndValue(inReverse);
+ if (mRepeatCount != INFINITE && currentPlayTime > (mRepeatCount + 1) * mDuration) {
+ throw new IllegalStateException("Can't animate a value outside of the duration");
} else {
// Find the current fraction:
- float fraction = currentPlayTime / (float) mDuration;
- fraction = getCurrentIterationFraction(fraction, inReverse);
+ float fraction = Math.max(0, currentPlayTime) / (float) mDuration;
+ fraction = getCurrentIterationFraction(fraction, false);
animateValue(fraction);
}
}
+ @Override
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ boolean inReverse = currentPlayTime < lastPlayTime;
+ boolean doSkip;
+ if (currentPlayTime <= 0 && lastPlayTime > 0) {
+ doSkip = true;
+ } else {
+ long duration = getTotalDuration();
+ doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
+ }
+ if (doSkip) {
+ if (notify) {
+ notifyStartListeners(inReverse);
+ }
+ skipToEndValue(inReverse);
+ if (notify) {
+ notifyEndListeners(inReverse);
+ }
+ }
+ }
+
/**
* Internal use only.
* Skips the animation value to end/start, depending on whether the play direction is forward
@@ -1641,6 +1688,9 @@
Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
(int) (fraction * 1000));
}
+ if (mValues == null) {
+ return;
+ }
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 9ecf8ff..88765c3 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -909,7 +909,6 @@
/**
* Clear the {@link BundleMerger} object that was previously set using
* {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
- *
* @hide
*/
public void clearDeliveryGroupExtrasMerger() {
@@ -917,6 +916,36 @@
}
/**
+ * Set PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ *
+ * @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
+ * of states
+ * @hide
+ */
+ @SystemApi
+ @Override
+ @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+ super.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+ }
+
+ /**
+ * Get PendingIntent activity is allowed to be started in the background if the caller can start
+ * background activities.
+ *
+ * @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
+ * targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
+ * not match the actual behavior if the value was not explicitly set.
+ * @hide
+ */
+ @SystemApi
+ @Override
+ @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+ return super.isPendingIntentBackgroundActivityLaunchAllowed();
+ }
+
+
+ /**
* Sets the mode for allowing or denying the senders privileges to start background activities
* to the PendingIntent.
*
@@ -924,7 +953,6 @@
* methods. A privileged sender of a PendingIntent should only grant
* MODE_BACKGROUND_ACTIVITY_START_ALLOWED if the PendingIntent is from a trusted source and/or
* executed on behalf the user.
- *
* @hide
*/
@SystemApi
@@ -936,6 +964,19 @@
}
/**
+ * Gets the mode for allowing or denying the senders privileges to start background activities
+ * to the PendingIntent.
+ *
+ * @see #setPendingIntentBackgroundActivityStartMode(int)
+ * @hide
+ */
+ @SystemApi
+ @Override // to narrow down the return type
+ public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
+ return super.getPendingIntentBackgroundActivityStartMode();
+ }
+
+ /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b91fa35..12899f2 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2028,7 +2028,7 @@
/** @hide */
@NonNull
@Override
- public IBinder getIApplicationThreadBinder() {
+ public IBinder getProcessToken() {
return getIApplicationThread().asBinder();
}
@@ -3062,7 +3062,14 @@
@Override
public boolean isDeviceContext() {
- return mIsExplicitDeviceId || isAssociatedWithDisplay();
+ if (mIsExplicitDeviceId) {
+ if (mDeviceId == VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+ return true;
+ }
+ VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
+ return vdm.isValidVirtualDeviceId(mDeviceId);
+ }
+ return isAssociatedWithDisplay();
}
@Override
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 5b3b2a6..1f5182a 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1335,7 +1335,7 @@
return new TimeZoneDetectorImpl();
}});
- registerService(Context.TIME_MANAGER, TimeManager.class,
+ registerService(Context.TIME_MANAGER_SERVICE, TimeManager.class,
new CachedServiceFetcher<TimeManager>() {
@Override
public TimeManager createService(ContextImpl ctx)
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 67408a4..e4ee959 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -158,6 +159,24 @@
*/
public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9;
+
+ /**
+ * Value for {@link #getHeadlessDeviceOwnerMode} which indicates that this DPC should not
+ * be provisioned into Device Owner mode on a Headless System User Mode device.
+ */
+ public static final int HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED = 0;
+
+ /**
+ * Value for {@link #getHeadlessDeviceOwnerMode} which indicates that this DPC should be
+ * provisioned into "affiliated" mode when on a Headless System User Mode device.
+ *
+ * <p>This mode adds a Profile Owner to all users other than the user the Device Owner is on.
+ */
+ public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
+
+ @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED})
+ private @interface HeadlessDeviceOwnerMode {}
+
/** @hide */
public static class PolicyInfo {
public final int ident;
@@ -255,6 +274,8 @@
*/
boolean mSupportsTransferOwnership;
+ @HeadlessDeviceOwnerMode int mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+
/**
* Constructor.
*
@@ -341,6 +362,17 @@
"support-transfer-ownership tag must be empty.");
}
mSupportsTransferOwnership = true;
+ } else if (tagName.equals("headless-system-user")) {
+ String deviceOwnerModeStringValue =
+ parser.getAttributeValue(null, "device-owner-mode");
+
+ if (deviceOwnerModeStringValue.equalsIgnoreCase("unsupported")) {
+ mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ } else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) {
+ mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+ } else {
+ throw new XmlPullParserException("headless-system-user mode must be valid");
+ }
}
}
} catch (NameNotFoundException e) {
@@ -355,6 +387,7 @@
mActivityInfo = ActivityInfo.CREATOR.createFromParcel(source);
mUsesPolicies = source.readInt();
mSupportsTransferOwnership = source.readBoolean();
+ mHeadlessDeviceOwnerMode = source.readInt();
}
/**
@@ -460,6 +493,14 @@
return mSupportsTransferOwnership;
}
+ /**
+ * Returns the mode this DeviceAdmin wishes to use if provisioned as a Device Owner on a
+ * headless system user mode device.
+ */
+ public @HeadlessDeviceOwnerMode int getHeadlessDeviceOwnerMode() {
+ return mHeadlessDeviceOwnerMode;
+ }
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public ArrayList<PolicyInfo> getUsedPolicies() {
@@ -505,6 +546,7 @@
mActivityInfo.writeToParcel(dest, flags);
dest.writeInt(mUsesPolicies);
dest.writeBoolean(mSupportsTransferOwnership);
+ dest.writeInt(mHeadlessDeviceOwnerMode);
}
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 11584cc..7e5523a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,10 +16,10 @@
package android.app.admin;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
@@ -2812,6 +2812,17 @@
public static final int STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15;
/**
+ * Result code for {@link #checkProvisioningPrecondition}.
+ *
+ * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when provisioning a DPC which does
+ * not support headless system user mode on a headless system user mode device.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16;
+
+ /**
* Result codes for {@link #checkProvisioningPrecondition} indicating all the provisioning pre
* conditions.
*
@@ -2824,7 +2835,8 @@
STATUS_HAS_PAIRED, STATUS_MANAGED_USERS_NOT_SUPPORTED, STATUS_SYSTEM_USER,
STATUS_CANNOT_ADD_MANAGED_PROFILE, STATUS_DEVICE_ADMIN_NOT_SUPPORTED,
STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
- STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS
+ STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS,
+ STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED
})
public @interface ProvisioningPrecondition {}
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
index e35e359..7cb6c34 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -41,7 +41,7 @@
* @hide
*/
@SystemApi
-@SystemService(Context.TIME_MANAGER)
+@SystemService(Context.TIME_MANAGER_SERVICE)
public final class TimeManager {
private static final String TAG = "time.TimeManager";
private static final boolean DEBUG = false;
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index e96a2c1..4f49b8d 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -18,6 +18,7 @@
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceParams;
import android.hardware.display.IVirtualDisplayCallback;
@@ -41,10 +42,12 @@
* @param params The parameters for creating this virtual device. See {@link
* VirtualDeviceManager.VirtualDeviceParams}.
* @param activityListener The listener to listen for activity changes in a virtual device.
+ * @param soundEffectListener The listener to listen for sound effect playback requests.
*/
IVirtualDevice createVirtualDevice(
in IBinder token, String packageName, int associationId,
- in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener);
+ in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener,
+ in IVirtualDeviceSoundEffectListener soundEffectListener);
/**
* Returns the details of all available virtual devices.
@@ -92,4 +95,13 @@
* if there's none.
*/
int getAudioRecordingSessionId(int deviceId);
+
+ /**
+ * Triggers sound effect playback on virtual device.
+ *
+ * @param deviceId id of the virtual device.
+ * @param sound effect type corresponding to
+ * {@code android.media.AudioManager.SystemSoundEffect}
+ */
+ void playSoundEffect(int deviceId, int effectType);
}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
new file mode 100644
index 0000000..91c209f
--- /dev/null
+++ b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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 android.companion.virtual;
+
+/**
+ * Interface to listen for sound effect playback on Virtual Device.
+ *
+ * @hide
+ */
+oneway interface IVirtualDeviceSoundEffectListener {
+
+
+ /**
+ * Called when there's sound effect to be played on Virtual Device.
+ *
+ * @param sound effect type corresponding to
+ * {@code android.media.AudioManager.SystemSoundEffect}
+ */
+ void onPlaySoundEffect(int effectType);
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 3bc1628..d585e8f 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -69,6 +69,8 @@
import android.util.Log;
import android.view.Surface;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -333,10 +335,15 @@
* @hide
*/
public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
- //TODO - handle requests to play sound effects by custom callbacks or SoundPool asociated
- // with device session id.
- // For now, this is intentionally left empty and effectively disables sound effects for
- // virtual devices with custom device audio policy.
+ if (mService == null) {
+ Log.w(TAG, "Failed to dispatch sound effect; no virtual device manager service.");
+ return;
+ }
+ try {
+ mService.playSoundEffect(deviceId, effectType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -354,11 +361,19 @@
private final Context mContext;
private final IVirtualDeviceManager mService;
private final IVirtualDevice mVirtualDevice;
+ private final Object mActivityListenersLock = new Object();
+ @GuardedBy("mActivityListenersLock")
private final ArrayMap<ActivityListener, ActivityListenerDelegate> mActivityListeners =
new ArrayMap<>();
+ private final Object mIntentInterceptorListenersLock = new Object();
+ @GuardedBy("mIntentInterceptorListenersLock")
private final ArrayMap<IntentInterceptorCallback,
VirtualIntentInterceptorDelegate> mIntentInterceptorListeners =
new ArrayMap<>();
+ private final Object mSoundEffectListenersLock = new Object();
+ @GuardedBy("mSoundEffectListenersLock")
+ private final ArrayMap<SoundEffectListener, SoundEffectListenerDelegate>
+ mSoundEffectListeners = new ArrayMap<>();
private final IVirtualDeviceActivityListener mActivityListenerBinder =
new IVirtualDeviceActivityListener.Stub() {
@@ -366,9 +381,11 @@
public void onTopActivityChanged(int displayId, ComponentName topActivity) {
final long token = Binder.clearCallingIdentity();
try {
- for (int i = 0; i < mActivityListeners.size(); i++) {
- mActivityListeners.valueAt(i)
- .onTopActivityChanged(displayId, topActivity);
+ synchronized (mActivityListenersLock) {
+ for (int i = 0; i < mActivityListeners.size(); i++) {
+ mActivityListeners.valueAt(i)
+ .onTopActivityChanged(displayId, topActivity);
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -379,8 +396,26 @@
public void onDisplayEmpty(int displayId) {
final long token = Binder.clearCallingIdentity();
try {
- for (int i = 0; i < mActivityListeners.size(); i++) {
- mActivityListeners.valueAt(i).onDisplayEmpty(displayId);
+ synchronized (mActivityListenersLock) {
+ for (int i = 0; i < mActivityListeners.size(); i++) {
+ mActivityListeners.valueAt(i).onDisplayEmpty(displayId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+ private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
+ new IVirtualDeviceSoundEffectListener.Stub() {
+ @Override
+ public void onPlaySoundEffect(int soundEffect) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSoundEffectListenersLock) {
+ for (int i = 0; i < mSoundEffectListeners.size(); i++) {
+ mSoundEffectListeners.valueAt(i).onPlaySoundEffect(soundEffect);
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -407,7 +442,8 @@
mContext.getPackageName(),
associationId,
params,
- mActivityListenerBinder);
+ mActivityListenerBinder,
+ mSoundEffectListener);
final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
@@ -932,7 +968,11 @@
*/
public void addActivityListener(
@CallbackExecutor @NonNull Executor executor, @NonNull ActivityListener listener) {
- mActivityListeners.put(listener, new ActivityListenerDelegate(listener, executor));
+ final ActivityListenerDelegate delegate = new ActivityListenerDelegate(
+ Objects.requireNonNull(listener), Objects.requireNonNull(executor));
+ synchronized (mActivityListenersLock) {
+ mActivityListeners.put(listener, delegate);
+ }
}
/**
@@ -943,7 +983,38 @@
* @see #addActivityListener(Executor, ActivityListener)
*/
public void removeActivityListener(@NonNull ActivityListener listener) {
- mActivityListeners.remove(listener);
+ synchronized (mActivityListenersLock) {
+ mActivityListeners.remove(Objects.requireNonNull(listener));
+ }
+ }
+
+ /**
+ * Adds a sound effect listener.
+ *
+ * @param executor The executor where the listener is executed on.
+ * @param soundEffectListener The listener to add.
+ * @see #removeActivityListener(ActivityListener)
+ */
+ public void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor,
+ @NonNull SoundEffectListener soundEffectListener) {
+ final SoundEffectListenerDelegate delegate =
+ new SoundEffectListenerDelegate(Objects.requireNonNull(executor),
+ Objects.requireNonNull(soundEffectListener));
+ synchronized (mSoundEffectListenersLock) {
+ mSoundEffectListeners.put(soundEffectListener, delegate);
+ }
+ }
+
+ /**
+ * Removes a sound effect listener previously added with {@link #addActivityListener}.
+ *
+ * @param soundEffectListener The listener to remove.
+ * @see #addActivityListener(Executor, ActivityListener)
+ */
+ public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) {
+ synchronized (mSoundEffectListenersLock) {
+ mSoundEffectListeners.remove(Objects.requireNonNull(soundEffectListener));
+ }
}
/**
@@ -972,7 +1043,9 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- mIntentInterceptorListeners.put(interceptorCallback, delegate);
+ synchronized (mIntentInterceptorListenersLock) {
+ mIntentInterceptorListeners.put(interceptorCallback, delegate);
+ }
}
/**
@@ -983,14 +1056,17 @@
public void unregisterIntentInterceptor(
@NonNull IntentInterceptorCallback interceptorCallback) {
Objects.requireNonNull(interceptorCallback);
- final VirtualIntentInterceptorDelegate delegate =
- mIntentInterceptorListeners.get(interceptorCallback);
- try {
- mVirtualDevice.unregisterIntentInterceptor(delegate);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ final VirtualIntentInterceptorDelegate delegate;
+ synchronized (mIntentInterceptorListenersLock) {
+ delegate = mIntentInterceptorListeners.remove(interceptorCallback);
}
- mIntentInterceptorListeners.remove(interceptorCallback);
+ if (delegate != null) {
+ try {
+ mVirtualDevice.unregisterIntentInterceptor(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
}
@@ -1090,4 +1166,38 @@
}
}
}
+
+ /**
+ * Listener for system sound effect playback on virtual device.
+ * @hide
+ */
+ @SystemApi
+ public interface SoundEffectListener {
+
+ /**
+ * Called when there's a system sound effect to be played on virtual device.
+ *
+ * @param effectType - system sound effect type, see
+ * {@code android.media.AudioManager.SystemSoundEffect}
+ */
+ void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType);
+ }
+
+ /**
+ * A wrapper for {@link SoundEffectListener} that executes callbacks on the given executor.
+ */
+ private static class SoundEffectListenerDelegate {
+ @NonNull private final SoundEffectListener mSoundEffectListener;
+ @NonNull private final Executor mExecutor;
+
+ private SoundEffectListenerDelegate(Executor executor,
+ SoundEffectListener soundEffectCallback) {
+ mSoundEffectListener = soundEffectCallback;
+ mExecutor = executor;
+ }
+
+ public void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType) {
+ mExecutor.execute(() -> mSoundEffectListener.onPlaySoundEffect(effectType));
+ }
+ }
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a58cac3..5f1502f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5854,7 +5854,7 @@
public static final String SECURE_ELEMENT_SERVICE = "secure_element";
/**
- * Use with {@link #getSystemService(String)} to retrieve an
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.app.timedetector.TimeDetector}.
* @hide
*
@@ -5863,7 +5863,7 @@
public static final String TIME_DETECTOR_SERVICE = "time_detector";
/**
- * Use with {@link #getSystemService(String)} to retrieve an
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.app.timezonedetector.TimeZoneDetector}.
* @hide
*
@@ -5872,12 +5872,14 @@
public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector";
/**
- * Use with {@link #getSystemService(String)} to retrieve an {@link TimeManager}.
+ * Use with {@link #getSystemService(String)} to retrieve a {@link TimeManager}.
* @hide
*
* @see #getSystemService(String)
*/
- public static final String TIME_MANAGER = "time_manager";
+ @SystemApi
+ @SuppressLint("ServiceName")
+ public static final String TIME_MANAGER_SERVICE = "time_manager";
/**
* Binder service name for {@link AppBindingService}.
@@ -7340,6 +7342,7 @@
* @see #createDeviceContext(int)
* @hide
*/
+ @TestApi
public void updateDeviceId(int deviceId) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -7376,10 +7379,12 @@
/**
* Indicates whether the value of {@link Context#getDeviceId()} can be relied upon for
* this instance. It will return {@code true} for Contexts created by
- * {@link Context#createDeviceContext(int)}, as well as for UI and Display Contexts.
+ * {@link Context#createDeviceContext(int)} which reference a valid device ID, as well as for
+ * UI and Display Contexts.
* <p>
* Contexts created with {@link Context#createDeviceContext(int)} will have an explicit
- * device association, which will never change. UI Contexts and Display Contexts are
+ * device association, which will never change, even if the underlying device is closed or is
+ * removed. UI Contexts and Display Contexts are
* already associated with a display, so if the device association is not explicitly
* given, {@link Context#getDeviceId()} will return the ID of the device associated with
* the associated display. The system can assign an arbitrary device id value for Contexts not
@@ -7527,14 +7532,12 @@
}
/**
- * Get the binder object associated with the IApplicationThread of this Context.
- *
- * This can be used by a mainline module to uniquely identify a specific app process.
+ * Used by a mainline module to uniquely identify a specific app process.
* @hide
*/
@NonNull
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public IBinder getIApplicationThreadBinder() {
+ public IBinder getProcessToken() {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index e65e91c..9027e2e 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -1286,8 +1286,8 @@
* @hide
*/
@Override
- public IBinder getIApplicationThreadBinder() {
- return mBase.getIApplicationThreadBinder();
+ public IBinder getProcessToken() {
+ return mBase.getProcessToken();
}
/**
diff --git a/core/java/android/content/pm/ApkChecksum.java b/core/java/android/content/pm/ApkChecksum.java
index d550f41..88a1960 100644
--- a/core/java/android/content/pm/ApkChecksum.java
+++ b/core/java/android/content/pm/ApkChecksum.java
@@ -36,7 +36,7 @@
*
* @see PackageManager#requestChecksums
*/
-@DataClass(genHiddenConstructor = true)
+@DataClass(genHiddenConstructor = true, genToString = true)
@DataClass.Suppress({"getChecksum"})
public final class ApkChecksum implements Parcelable {
/**
@@ -178,6 +178,20 @@
@Override
@DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ApkChecksum { " +
+ "splitName = " + mSplitName + ", " +
+ "checksum = " + mChecksum + ", " +
+ "installerPackageName = " + mInstallerPackageName + ", " +
+ "installerCertificate = " + java.util.Arrays.toString(mInstallerCertificate) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -235,10 +249,10 @@
};
@DataClass.Generated(
- time = 1619810171079L,
+ time = 1674080488372L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/ApkChecksum.java",
- inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable java.lang.String mInstallerPackageName\nprivate final @android.annotation.Nullable byte[] mInstallerCertificate\npublic @android.content.pm.Checksum.Type int getType()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getInstallerCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getInstallerCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable java.lang.String mInstallerPackageName\nprivate final @android.annotation.Nullable byte[] mInstallerCertificate\npublic @android.content.pm.Checksum.Type int getType()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getInstallerCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getInstallerCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 44747fa..e38cb65 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -406,24 +406,6 @@
}
/**
- * Returns true if the user is a split system user.
- * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
- * the method always returns false.
- */
- public boolean isSystemOnly() {
- return isSystemOnly(id);
- }
-
- /**
- * Returns true if the given user is a split system user.
- * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
- * the method always returns false.
- */
- public static boolean isSystemOnly(int userId) {
- return userId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser();
- }
-
- /**
* @return true if this user can be switched to.
**/
public boolean supportsSwitchTo() {
@@ -454,7 +436,7 @@
if (isProfile() || isGuest() || isRestricted()) {
return false;
}
- if (UserManager.isSplitSystemUser() || UserManager.isHeadlessSystemUserMode()) {
+ if (UserManager.isHeadlessSystemUserMode()) {
return id != UserHandle.USER_SYSTEM;
} else {
return id == UserHandle.USER_SYSTEM;
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 51662af..d75205c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -19,6 +19,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Slog;
@@ -36,7 +38,10 @@
/**
* Class holding the properties of a user that derive mostly from its user type.
+ *
+ * @hide
*/
+@SystemApi
public final class UserProperties implements Parcelable {
private static final String LOG_TAG = UserProperties.class.getSimpleName();
@@ -52,6 +57,10 @@
"crossProfileIntentFilterAccessControl";
private static final String ATTR_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY =
"crossProfileIntentResolutionStrategy";
+ private static final String ATTR_MEDIA_SHARED_WITH_PARENT =
+ "mediaSharedWithParent";
+ private static final String ATTR_CREDENTIAL_SHARABLE_WITH_PARENT =
+ "credentialSharableWithParent";
/** Index values of each property (to indicate whether they are present in this object). */
@IntDef(prefix = "INDEX_", value = {
@@ -62,7 +71,9 @@
INDEX_USE_PARENTS_CONTACTS,
INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA,
INDEX_CROSS_PROFILE_INTENT_FILTER_ACCESS_CONTROL,
- INDEX_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY
+ INDEX_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY,
+ INDEX_MEDIA_SHARED_WITH_PARENT,
+ INDEX_CREDENTIAL_SHARABLE_WITH_PARENT
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
@@ -75,6 +86,8 @@
private static final int INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA = 5;
private static final int INDEX_CROSS_PROFILE_INTENT_FILTER_ACCESS_CONTROL = 6;
private static final int INDEX_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY = 7;
+ private static final int INDEX_MEDIA_SHARED_WITH_PARENT = 8;
+ private static final int INDEX_CREDENTIAL_SHARABLE_WITH_PARENT = 9;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -95,16 +108,22 @@
* Suggests that the launcher should show this user's apps in the main tab.
* That is, either this user is a full user, so its apps should be presented accordingly, or, if
* this user is a profile, then its apps should be shown alongside its parent's apps.
+ * @hide
*/
+ @TestApi
public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0;
/**
* Suggests that the launcher should show this user's apps, but separately from the apps of this
* user's parent.
+ * @hide
*/
+ @TestApi
public static final int SHOW_IN_LAUNCHER_SEPARATE = 1;
/**
* Suggests that the launcher should not show this user.
+ * @hide
*/
+ @TestApi
public static final int SHOW_IN_LAUNCHER_NO = 2;
/**
@@ -304,6 +323,8 @@
}
// Add items that have no permission requirements at all.
setShowInLauncher(orig.getShowInLauncher());
+ setIsMediaSharedWithParent(orig.getIsMediaSharedWithParent());
+ setIsCredentialSharableWithParent(orig.getIsCredentialSharableWithParent());
}
/**
@@ -337,7 +358,9 @@
* and {@link #SHOW_IN_LAUNCHER_NO}.
*
* @return whether, and how, a profile should be shown in the Launcher.
+ * @hide
*/
+ @TestApi
public @ShowInLauncher int getShowInLauncher() {
if (isPresent(INDEX_SHOW_IN_LAUNCHER)) return mShowInLauncher;
if (mDefaultProperties != null) return mDefaultProperties.mShowInLauncher;
@@ -463,13 +486,45 @@
throw new SecurityException("You don't have permission to query "
+ "updateCrossProfileIntentFiltersOnOTA");
}
-
/** @hide */
public void setUpdateCrossProfileIntentFiltersOnOTA(boolean val) {
this.mUpdateCrossProfileIntentFiltersOnOTA = val;
setPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA);
}
+ /**
+ * Returns whether a profile shares media with its parent user.
+ * This only applies for users that have parents (i.e. for profiles).
+ */
+ public boolean getIsMediaSharedWithParent() {
+ if (isPresent(INDEX_MEDIA_SHARED_WITH_PARENT)) return mMediaSharedWithParent;
+ if (mDefaultProperties != null) return mDefaultProperties.mMediaSharedWithParent;
+ throw new SecurityException("You don't have permission to query isMediaSharedWithParent");
+ }
+ /** @hide */
+ public void setIsMediaSharedWithParent(boolean val) {
+ this.mMediaSharedWithParent = val;
+ setPresent(INDEX_MEDIA_SHARED_WITH_PARENT);
+ }
+ private boolean mMediaSharedWithParent;
+
+ /**
+ * Returns whether a profile can have shared lockscreen credential with its parent user.
+ * This only applies for users that have parents (i.e. for profiles).
+ */
+ public boolean getIsCredentialSharableWithParent() {
+ if (isPresent(INDEX_CREDENTIAL_SHARABLE_WITH_PARENT)) return mCredentialSharableWithParent;
+ if (mDefaultProperties != null) return mDefaultProperties.mCredentialSharableWithParent;
+ throw new SecurityException(
+ "You don't have permission to query isCredentialSharableWithParent");
+ }
+ /** @hide */
+ public void setIsCredentialSharableWithParent(boolean val) {
+ this.mCredentialSharableWithParent = val;
+ setPresent(INDEX_CREDENTIAL_SHARABLE_WITH_PARENT);
+ }
+ private boolean mCredentialSharableWithParent;
+
/*
Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
OTA update between user-parent
@@ -550,6 +605,8 @@
+ getCrossProfileIntentFilterAccessControl()
+ ", mCrossProfileIntentResolutionStrategy="
+ getCrossProfileIntentResolutionStrategy()
+ + ", mMediaSharedWithParent=" + getIsMediaSharedWithParent()
+ + ", mCredentialSharableWithParent=" + getIsCredentialSharableWithParent()
+ "}";
}
@@ -572,6 +629,9 @@
+ getCrossProfileIntentFilterAccessControl());
pw.println(prefix + " mCrossProfileIntentResolutionStrategy="
+ getCrossProfileIntentResolutionStrategy());
+ pw.println(prefix + " mMediaSharedWithParent=" + getIsMediaSharedWithParent());
+ pw.println(prefix + " mCredentialSharableWithParent="
+ + getIsCredentialSharableWithParent());
}
/**
@@ -629,6 +689,12 @@
case ATTR_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY:
setCrossProfileIntentResolutionStrategy(parser.getAttributeInt(i));
break;
+ case ATTR_MEDIA_SHARED_WITH_PARENT:
+ setIsMediaSharedWithParent(parser.getAttributeBoolean(i));
+ break;
+ case ATTR_CREDENTIAL_SHARABLE_WITH_PARENT:
+ setIsCredentialSharableWithParent(parser.getAttributeBoolean(i));
+ break;
default:
Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
}
@@ -676,6 +742,14 @@
serializer.attributeInt(null, ATTR_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY,
mCrossProfileIntentResolutionStrategy);
}
+ if (isPresent(INDEX_MEDIA_SHARED_WITH_PARENT)) {
+ serializer.attributeBoolean(null, ATTR_MEDIA_SHARED_WITH_PARENT,
+ mMediaSharedWithParent);
+ }
+ if (isPresent(INDEX_CREDENTIAL_SHARABLE_WITH_PARENT)) {
+ serializer.attributeBoolean(null, ATTR_CREDENTIAL_SHARABLE_WITH_PARENT,
+ mCredentialSharableWithParent);
+ }
}
// For use only with an object that has already had any permission-lacking fields stripped out.
@@ -690,6 +764,8 @@
dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA);
dest.writeInt(mCrossProfileIntentFilterAccessControl);
dest.writeInt(mCrossProfileIntentResolutionStrategy);
+ dest.writeBoolean(mMediaSharedWithParent);
+ dest.writeBoolean(mCredentialSharableWithParent);
}
/**
@@ -708,6 +784,8 @@
mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean();
mCrossProfileIntentFilterAccessControl = source.readInt();
mCrossProfileIntentResolutionStrategy = source.readInt();
+ mMediaSharedWithParent = source.readBoolean();
+ mCredentialSharableWithParent = source.readBoolean();
}
@Override
@@ -743,6 +821,8 @@
CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_ALL;
private @CrossProfileIntentResolutionStrategy int mCrossProfileIntentResolutionStrategy =
CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT;
+ private boolean mMediaSharedWithParent = false;
+ private boolean mCredentialSharableWithParent = false;
public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
mShowInLauncher = showInLauncher;
@@ -794,6 +874,16 @@
return this;
}
+ public Builder setIsMediaSharedWithParent(boolean mediaSharedWithParent) {
+ mMediaSharedWithParent = mediaSharedWithParent;
+ return this;
+ }
+
+ public Builder setIsCredentialSharableWithParent(boolean credentialSharableWithParent) {
+ mCredentialSharableWithParent = credentialSharableWithParent;
+ return this;
+ }
+
/** Builds a UserProperties object with *all* values populated. */
public UserProperties build() {
return new UserProperties(
@@ -804,7 +894,9 @@
mUseParentsContacts,
mUpdateCrossProfileIntentFiltersOnOTA,
mCrossProfileIntentFilterAccessControl,
- mCrossProfileIntentResolutionStrategy);
+ mCrossProfileIntentResolutionStrategy,
+ mMediaSharedWithParent,
+ mCredentialSharableWithParent);
}
} // end Builder
@@ -816,7 +908,9 @@
@InheritDevicePolicy int inheritDevicePolicy,
boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA,
@CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl,
- @CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy) {
+ @CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy,
+ boolean mediaSharedWithParent,
+ boolean credentialSharableWithParent) {
mDefaultProperties = null;
setShowInLauncher(showInLauncher);
@@ -827,5 +921,7 @@
setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA);
setCrossProfileIntentFilterAccessControl(crossProfileIntentFilterAccessControl);
setCrossProfileIntentResolutionStrategy(crossProfileIntentResolutionStrategy);
+ setIsMediaSharedWithParent(mediaSharedWithParent);
+ setIsCredentialSharableWithParent(credentialSharableWithParent);
}
}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index c77a372..ffdc7b38 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -42,7 +42,7 @@
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
- new float[] { 9.2f, 11.5f, 13.8f, 16.1f, 20.7f, 23f, 27.6f, 34.5f, 115})
+ new float[] { 9.2f, 11.5f, 13.8f, 16.4f, 19.8f, 21.8f, 25.2f, 30f, 100})
);
put(
@@ -51,7 +51,7 @@
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
- new float[] {10.4f, 13f, 15.6f, 18.2f, 23.4f, 26f, 31.2f, 39f, 130})
+ new float[] {10.4f, 13f, 15.6f, 18.8f, 21.6f, 23.6f, 26.4f, 30f, 100})
);
put(
@@ -60,7 +60,7 @@
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
- new float[] { 12f, 15f, 18f, 21f, 27f, 30f, 36f, 45f, 150})
+ new float[] { 12f, 15f, 18f, 22f, 24f, 26f, 28f, 30f, 100})
);
put(
@@ -69,7 +69,7 @@
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
- new float[] {14.4f, 18f, 21.6f, 25.2f, 32.4f, 36f, 43.2f, 54f, 180})
+ new float[] {14.4f, 18f, 21.6f, 24.4f, 27.6f, 30.8f, 32.8f, 34.8f, 100})
);
put(
@@ -78,7 +78,7 @@
/* fromSp= */
new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
/* toDp= */
- new float[] { 16f, 20f, 24f, 28f, 36f, 40f, 48f, 60f, 200})
+ new float[] { 16f, 20f, 24f, 26f, 30f, 34f, 36f, 38f, 100})
);
}
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
index fefa60a..84cc9a8 100644
--- a/core/java/android/credentials/CreateCredentialException.java
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -28,7 +28,7 @@
/**
* Represents an error encountered during the
- * {@link CredentialManager#executeCreateCredential(CreateCredentialRequest,
+ * {@link CredentialManager#createCredential(CreateCredentialRequest,
* Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
*/
public class CreateCredentialException extends Exception {
@@ -41,7 +41,7 @@
/**
* The error type value for when no credential is available for the given {@link
- * CredentialManager#executeCreateCredential(CreateCredentialRequest, Activity,
+ * CredentialManager#createCredential(CreateCredentialRequest, Activity,
* CancellationSignal, Executor, OutcomeReceiver)} request.
*/
@NonNull
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index be887a5..26f8831 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -54,7 +54,7 @@
/**
* Determines whether the request must only be fulfilled by a system provider.
*/
- private final boolean mRequireSystemProvider;
+ private final boolean mIsSystemProviderRequired;
/**
* Returns the requested credential type.
@@ -99,8 +99,8 @@
* Returns true if the request must only be fulfilled by a system provider, and false
* otherwise.
*/
- public boolean requireSystemProvider() {
- return mRequireSystemProvider;
+ public boolean isSystemProviderRequired() {
+ return mIsSystemProviderRequired;
}
@Override
@@ -108,7 +108,7 @@
dest.writeString8(mType);
dest.writeBundle(mCredentialData);
dest.writeBundle(mCandidateQueryData);
- dest.writeBoolean(mRequireSystemProvider);
+ dest.writeBoolean(mIsSystemProviderRequired);
}
@Override
@@ -122,7 +122,7 @@
+ "type=" + mType
+ ", credentialData=" + mCredentialData
+ ", candidateQueryData=" + mCandidateQueryData
- + ", requireSystemProvider=" + mRequireSystemProvider
+ + ", isSystemProviderRequired=" + mIsSystemProviderRequired
+ "}";
}
@@ -133,7 +133,8 @@
* @param credentialData the full credential creation request data
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial creation candidate query stage
- * @param requireSystemProvider whether the request must only be fulfilled by a system provider
+ * @param isSystemProviderRequired whether the request must only be fulfilled by a system
+ * provider
*
* @throws IllegalArgumentException If type is empty.
*/
@@ -141,19 +142,19 @@
@NonNull String type,
@NonNull Bundle credentialData,
@NonNull Bundle candidateQueryData,
- boolean requireSystemProvider) {
+ boolean isSystemProviderRequired) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
- mRequireSystemProvider = requireSystemProvider;
+ mIsSystemProviderRequired = isSystemProviderRequired;
}
private CreateCredentialRequest(@NonNull Parcel in) {
String type = in.readString8();
Bundle credentialData = in.readBundle();
Bundle candidateQueryData = in.readBundle();
- boolean requireSystemProvider = in.readBoolean();
+ boolean isSystemProviderRequired = in.readBoolean();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
@@ -161,7 +162,7 @@
AnnotationValidations.validate(NonNull.class, null, mCredentialData);
mCandidateQueryData = candidateQueryData;
AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
- mRequireSystemProvider = requireSystemProvider;
+ mIsSystemProviderRequired = isSystemProviderRequired;
}
public static final @NonNull Parcelable.Creator<CreateCredentialRequest> CREATOR =
diff --git a/core/java/android/credentials/CredentialDescription.aidl b/core/java/android/credentials/CredentialDescription.aidl
new file mode 100644
index 0000000..1b5739e
--- /dev/null
+++ b/core/java/android/credentials/CredentialDescription.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 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 android.credentials;
+
+/**
+ * @hide
+ */
+parcelable CredentialDescription;
\ No newline at end of file
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
new file mode 100644
index 0000000..ec6a396
--- /dev/null
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -0,0 +1,141 @@
+/*
+ * 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 android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.credentials.CredentialEntry;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents the type and contained data fields of a {@link Credential}.
+ * @hide
+ */
+@TestApi
+public final class CredentialDescription implements Parcelable {
+
+ /**
+ * The credential type.
+ */
+ @NonNull
+ private final String mType;
+
+ /**
+ * The flattened JSON string that will be matched with requests.
+ */
+ @NonNull
+ private final String mFlattenedRequestString;
+
+ /**
+ * The entry to be used in the UI.
+ */
+ @NonNull
+ private final List<CredentialEntry> mCredentialEntries;
+
+ /**
+ * Constructs a {@link CredentialDescription}.
+ *
+ * @param type the type of the credential returned.
+ * @param flattenedRequestString flattened JSON string that will be matched with requests.
+ * @param credentialEntries a list of {@link CredentialEntry}s that have been returned
+ * to the developer upon credential creation.
+ *
+ * @throws IllegalArgumentException If type is empty.
+ */
+ public CredentialDescription(@NonNull String type,
+ @NonNull String flattenedRequestString,
+ @NonNull List<CredentialEntry> credentialEntries) {
+ mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
+ mFlattenedRequestString = Preconditions.checkStringNotEmpty(flattenedRequestString);
+ mCredentialEntries = Objects.requireNonNull(credentialEntries);
+ }
+
+ private CredentialDescription(@NonNull Parcel in) {
+ String type = in.readString8();
+ String flattenedRequestString = in.readString();
+ List<CredentialEntry> entries = new ArrayList<>();
+ in.readTypedList(entries, CredentialEntry.CREATOR);
+
+ mType = type;
+ AnnotationValidations.validate(android.annotation.NonNull.class, null, mType);
+ mFlattenedRequestString = flattenedRequestString;
+ AnnotationValidations.validate(android.annotation.NonNull.class, null,
+ mFlattenedRequestString);
+ mCredentialEntries = entries;
+ AnnotationValidations.validate(android.annotation.NonNull.class, null,
+ mCredentialEntries);
+ }
+
+ public static final @NonNull Parcelable.Creator<CredentialDescription> CREATOR =
+ new Parcelable.Creator<CredentialDescription>() {
+ @Override
+ public CredentialDescription createFromParcel(Parcel in) {
+ return new CredentialDescription(in);
+ }
+
+ @Override
+ public CredentialDescription[] newArray(int size) {
+ return new CredentialDescription[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mType);
+ dest.writeString(mFlattenedRequestString);
+ dest.writeTypedList(mCredentialEntries, flags);
+ }
+
+ @NonNull
+ public String getType() {
+ return mType;
+ }
+
+ @NonNull
+ public String getFlattenedRequestString() {
+ return mFlattenedRequestString;
+ }
+
+ @NonNull
+ public List<CredentialEntry> getCredentialEntries() {
+ return mCredentialEntries;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mFlattenedRequestString);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return Objects.equals(mType, ((CredentialDescription) obj).getType())
+ && Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj).getType());
+ }
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index d4daf364..232d063 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
@@ -63,6 +64,14 @@
"enable_credential_manager";
/**
+ * Flag to enable and disable Credential Description api.
+ *
+ * @hide
+ */
+ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+ "enable_credential_description_api";
+
+ /**
* @hide instantiated by ContextImpl.
*/
public CredentialManager(Context context, ICredentialManager service) {
@@ -82,7 +91,7 @@
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
- public void executeGetCredential(
+ public void getCredential(
@NonNull GetCredentialRequest request,
@NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@@ -94,7 +103,7 @@
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "executeGetCredential already canceled");
+ Log.w(TAG, "getCredential already canceled");
return;
}
@@ -126,7 +135,7 @@
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
- public void executeCreateCredential(
+ public void createCredential(
@NonNull CreateCredentialRequest request,
@NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@@ -139,7 +148,7 @@
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "executeCreateCredential already canceled");
+ Log.w(TAG, "createCredential already canceled");
return;
}
@@ -185,7 +194,7 @@
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "executeCreateCredential already canceled");
+ Log.w(TAG, "clearCredentialState already canceled");
return;
}
@@ -294,6 +303,114 @@
true);
}
+ /**
+ * Returns whether the credential description api is enabled.
+ *
+ * @hide
+ */
+ public static boolean isCredentialDescriptionApiEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
+ }
+
+ /**
+ * Registers a {@link CredentialDescription} for an actively provisioned {@link Credential}
+ * a CredentialProvider has. This registry will then be used by
+ * {@link #executeGetCredential(GetCredentialRequest, Activity,
+ * CancellationSignal, Executor, OutcomeReceiver)} to determine where to
+ * fetch the requested {@link Credential} from.
+ *
+ *
+ * @param request the request data
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ *
+ * @throws {@link UnsupportedOperationException} if the feature has not been enabled.
+ *
+ * @hide
+ */
+ @TestApi
+ public void registerCredentialDescription(
+ @NonNull RegisterCredentialDescriptionRequest request,
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
+
+ if (!isCredentialDescriptionApiEnabled()) {
+ throw new UnsupportedOperationException("This API is not currently supported.");
+ }
+
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "executeCreateCredential already canceled");
+ return;
+ }
+
+ ICancellationSignal cancelRemote = null;
+ try {
+ cancelRemote = mService.registerCredentialDescription(request,
+ new RegisterCredentialDescriptionTransport(executor, callback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (cancellationSignal != null && cancelRemote != null) {
+ cancellationSignal.setRemote(cancelRemote);
+ }
+ }
+
+
+ /**
+ * Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
+ * that has been registered previously.
+ *
+ *
+ * @param request the request data
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ *
+ * @throws {@link UnsupportedOperationException} if the feature has not been enabled.
+ *
+ * @hide
+ */
+ @TestApi
+ public void unRegisterCredentialDescription(
+ @NonNull UnregisterCredentialDescriptionRequest request,
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
+
+ if (!isCredentialDescriptionApiEnabled()) {
+ throw new UnsupportedOperationException("This API is not currently supported.");
+ }
+
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "executeCreateCredential already canceled");
+ return;
+ }
+
+ ICancellationSignal cancelRemote = null;
+ try {
+ cancelRemote = mService.unRegisterCredentialDescription(request,
+ new UnregisterCredentialDescriptionTransport(executor, callback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (cancellationSignal != null && cancelRemote != null) {
+ cancellationSignal.setRemote(cancelRemote);
+ }
+ }
+
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
@@ -455,4 +572,54 @@
() -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
}
}
+
+ private static class RegisterCredentialDescriptionTransport
+ extends IRegisterCredentialDescriptionCallback.Stub {
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, RegisterCredentialDescriptionException> mCallback;
+
+ private RegisterCredentialDescriptionTransport(Executor executor,
+ OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResponse() {
+ mCallback.onResult(null);
+ }
+
+ @Override
+ public void onError(String errorCode, String message) {
+ mExecutor.execute(
+ () -> mCallback.onError(new RegisterCredentialDescriptionException(errorCode,
+ message)));
+ }
+ }
+
+ private static class UnregisterCredentialDescriptionTransport
+ extends IUnregisterCredentialDescriptionCallback.Stub {
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, UnregisterCredentialDescriptionException> mCallback;
+
+ private UnregisterCredentialDescriptionTransport(Executor executor,
+ OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResponse() {
+ mCallback.onResult(null);
+ }
+
+ @Override
+ public void onError(String errorCode, String message) {
+ mExecutor.execute(
+ () -> mCallback.onError(new UnregisterCredentialDescriptionException(errorCode,
+ message)));
+ }
+ }
}
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
index 478afff..720c53b 100644
--- a/core/java/android/credentials/GetCredentialException.java
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -28,7 +28,7 @@
/**
* Represents an error encountered during the
- * {@link CredentialManager#executeGetCredential(GetCredentialRequest,
+ * {@link CredentialManager#getCredential(GetCredentialRequest,
* Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
*/
public class GetCredentialException extends Exception {
@@ -41,7 +41,7 @@
/**
* The error type value for when no credential is found available for the given {@link
- * CredentialManager#executeGetCredential(GetCredentialRequest, Activity, CancellationSignal,
+ * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal,
* Executor, OutcomeReceiver)} request.
*/
@NonNull
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index 47731dd..55daf86 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -53,7 +53,7 @@
/**
* Determines whether the request must only be fulfilled by a system provider.
*/
- private final boolean mRequireSystemProvider;
+ private final boolean mIsSystemProviderRequired;
/**
* Returns the requested credential type.
@@ -91,8 +91,8 @@
* Returns true if the request must only be fulfilled by a system provider, and false
* otherwise.
*/
- public boolean requireSystemProvider() {
- return mRequireSystemProvider;
+ public boolean isSystemProviderRequired() {
+ return mIsSystemProviderRequired;
}
@Override
@@ -100,7 +100,7 @@
dest.writeString8(mType);
dest.writeBundle(mCredentialRetrievalData);
dest.writeBundle(mCandidateQueryData);
- dest.writeBoolean(mRequireSystemProvider);
+ dest.writeBoolean(mIsSystemProviderRequired);
}
@Override
@@ -114,7 +114,7 @@
+ "type=" + mType
+ ", requestData=" + mCredentialRetrievalData
+ ", candidateQueryData=" + mCandidateQueryData
- + ", requireSystemProvider=" + mRequireSystemProvider
+ + ", isSystemProviderRequired=" + mIsSystemProviderRequired
+ "}";
}
@@ -125,7 +125,7 @@
* @param credentialRetrievalData the request data
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial credential candidate query stage
- * @param requireSystemProvider whether the request must only be fulfilled by a system
+ * @param isSystemProviderRequired whether the request must only be fulfilled by a system
* provider
* @throws IllegalArgumentException If type is empty.
*/
@@ -133,20 +133,20 @@
@NonNull String type,
@NonNull Bundle credentialRetrievalData,
@NonNull Bundle candidateQueryData,
- boolean requireSystemProvider) {
+ boolean isSystemProviderRequired) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
"requestData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
- mRequireSystemProvider = requireSystemProvider;
+ mIsSystemProviderRequired = isSystemProviderRequired;
}
private GetCredentialOption(@NonNull Parcel in) {
String type = in.readString8();
Bundle data = in.readBundle();
Bundle candidateQueryData = in.readBundle();
- boolean requireSystemProvider = in.readBoolean();
+ boolean isSystemProviderRequired = in.readBoolean();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
@@ -154,7 +154,7 @@
AnnotationValidations.validate(NonNull.class, null, mCredentialRetrievalData);
mCandidateQueryData = candidateQueryData;
AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
- mRequireSystemProvider = requireSystemProvider;
+ mIsSystemProviderRequired = isSystemProviderRequired;
}
public static final @NonNull Parcelable.Creator<GetCredentialOption> CREATOR =
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index c3ca03d..75b3d0c 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -21,10 +21,14 @@
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.credentials.IListEnabledProvidersCallback;
+import android.credentials.IRegisterCredentialDescriptionCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.os.ICancellationSignal;
@@ -44,4 +48,9 @@
@nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
+
+ @nullable ICancellationSignal registerCredentialDescription(in RegisterCredentialDescriptionRequest request, in IRegisterCredentialDescriptionCallback callback, String callingPackage);
+
+ @nullable ICancellationSignal unRegisterCredentialDescription(in UnregisterCredentialDescriptionRequest request, in IUnregisterCredentialDescriptionCallback callback, String callingPackage);
}
+
diff --git a/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl b/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl
new file mode 100644
index 0000000..124a319
--- /dev/null
+++ b/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 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 android.credentials;
+
+/**
+ * Listener for an registerCredentialDescription request.
+ *
+ * @hide
+ */
+interface IRegisterCredentialDescriptionCallback {
+ oneway void onResponse();
+ oneway void onError(String errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl b/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl
new file mode 100644
index 0000000..b30a12a
--- /dev/null
+++ b/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 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 android.credentials;
+
+/**
+ * Listener for an registerCredentialDescription request.
+ *
+ * @hide
+ */
+interface IUnregisterCredentialDescriptionCallback {
+ oneway void onResponse();
+ oneway void onError(String errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionException.java b/core/java/android/credentials/RegisterCredentialDescriptionException.java
new file mode 100644
index 0000000..d19bb8e
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 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 android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+@TestApi
+public class RegisterCredentialDescriptionException extends Exception {
+
+ @NonNull public final String errorType;
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public RegisterCredentialDescriptionException(@NonNull String errorType,
+ @Nullable String message) {
+ this(errorType, message, null);
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public RegisterCredentialDescriptionException(
+ @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.errorType =
+ Preconditions
+ .checkStringNotEmpty(errorType, "errorType must not be empty");
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public RegisterCredentialDescriptionException(@NonNull String errorType,
+ @Nullable Throwable cause) {
+ this(errorType, null, cause);
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public RegisterCredentialDescriptionException(@NonNull String errorType) {
+ this(errorType, null, null);
+ }
+}
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl b/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl
new file mode 100644
index 0000000..1d56728
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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 android.credentials;
+
+parcelable RegisterCredentialDescriptionRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionRequest.java b/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
new file mode 100644
index 0000000..f257ac5
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
@@ -0,0 +1,95 @@
+/*
+ * 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 android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * A request to register a {@link ComponentName} that contains an actively provisioned
+ * {@link Credential} represented by a {@link CredentialDescription}.
+ *
+ * @hide
+ */
+@TestApi
+public final class RegisterCredentialDescriptionRequest implements Parcelable {
+
+ public static final String FLATTENED_REQUEST_STRING_KEY = "flattened_request_string";
+
+ @NonNull
+ private final List<CredentialDescription> mCredentialDescriptions;
+
+ public RegisterCredentialDescriptionRequest(
+ @NonNull CredentialDescription credentialDescription) {
+ mCredentialDescriptions = Arrays.asList(requireNonNull(credentialDescription));
+ }
+
+ public RegisterCredentialDescriptionRequest(
+ @NonNull List<CredentialDescription> credentialDescriptions) {
+ mCredentialDescriptions = new ArrayList<>(requireNonNull(credentialDescriptions));
+ }
+
+ private RegisterCredentialDescriptionRequest(@NonNull Parcel in) {
+ List<CredentialDescription> credentialDescriptions = new ArrayList<>();
+ in.readTypedList(credentialDescriptions, CredentialDescription.CREATOR);
+
+ mCredentialDescriptions = new ArrayList<>();
+ AnnotationValidations.validate(android.annotation.NonNull.class, null,
+ credentialDescriptions);
+ mCredentialDescriptions.addAll(credentialDescriptions);
+ }
+
+ public static final @NonNull Parcelable.Creator<RegisterCredentialDescriptionRequest> CREATOR =
+ new Parcelable.Creator<RegisterCredentialDescriptionRequest>() {
+ @Override
+ public RegisterCredentialDescriptionRequest createFromParcel(Parcel in) {
+ return new RegisterCredentialDescriptionRequest(in);
+ }
+
+ @Override
+ public RegisterCredentialDescriptionRequest[] newArray(int size) {
+ return new RegisterCredentialDescriptionRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mCredentialDescriptions, flags);
+ }
+
+ @NonNull
+ public List<CredentialDescription> getCredentialDescriptions() {
+ return mCredentialDescriptions;
+ }
+}
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionException.java b/core/java/android/credentials/UnregisterCredentialDescriptionException.java
new file mode 100644
index 0000000..a16915a
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 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 android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+@TestApi
+public class UnregisterCredentialDescriptionException extends Exception {
+
+ @NonNull public final String errorType;
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public UnregisterCredentialDescriptionException(@NonNull String errorType,
+ @Nullable String message) {
+ this(errorType, message, null);
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public UnregisterCredentialDescriptionException(
+ @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.errorType =
+ Preconditions
+ .checkStringNotEmpty(errorType, "errorType must not be empty");
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public UnregisterCredentialDescriptionException(@NonNull String errorType,
+ @Nullable Throwable cause) {
+ this(errorType, null, cause);
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public UnregisterCredentialDescriptionException(@NonNull String errorType) {
+ this(errorType, null, null);
+ }
+}
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl
new file mode 100644
index 0000000..e25f13c
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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 android.credentials;
+
+parcelable UnregisterCredentialDescriptionRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
new file mode 100644
index 0000000..6cf40e7
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+
+/**
+ * A request to unregister a {@link ComponentName} that contains an actively provisioned
+ * {@link Credential} represented by a {@link CredentialDescription}. *
+ *
+ * @hide
+ */
+@TestApi
+public final class UnregisterCredentialDescriptionRequest implements Parcelable {
+
+ @NonNull
+ private final CredentialDescription mCredentialDescription;
+
+ public UnregisterCredentialDescriptionRequest(@NonNull CredentialDescription
+ credentialDescription) {
+ mCredentialDescription = requireNonNull(credentialDescription);
+ }
+
+ private UnregisterCredentialDescriptionRequest(@NonNull Parcel in) {
+ CredentialDescription credentialDescription =
+ CredentialDescription.CREATOR.createFromParcel(in);
+
+ mCredentialDescription = credentialDescription;
+ AnnotationValidations.validate(android.annotation.NonNull.class, null,
+ credentialDescription);
+ }
+
+ public static final @NonNull Parcelable.Creator<UnregisterCredentialDescriptionRequest>
+ CREATOR = new Parcelable.Creator<UnregisterCredentialDescriptionRequest>() {
+ @Override
+ public UnregisterCredentialDescriptionRequest createFromParcel(Parcel in) {
+ return new UnregisterCredentialDescriptionRequest(in);
+ }
+
+ @Override
+ public UnregisterCredentialDescriptionRequest[] newArray(int size) {
+ return new UnregisterCredentialDescriptionRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mCredentialDescription.writeToParcel(dest, flags);
+ }
+
+ @NonNull
+ public CredentialDescription getCredentialDescription() {
+ return mCredentialDescription;
+ }
+}
diff --git a/core/java/android/credentials/ui/BaseDialogResult.java b/core/java/android/credentials/ui/BaseDialogResult.java
index cf5f036..5223314 100644
--- a/core/java/android/credentials/ui/BaseDialogResult.java
+++ b/core/java/android/credentials/ui/BaseDialogResult.java
@@ -16,6 +16,7 @@
package android.credentials.ui;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -25,6 +26,9 @@
import com.android.internal.util.AnnotationValidations;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Base dialog result data.
*
@@ -55,29 +59,27 @@
private static final String EXTRA_BASE_RESULT =
"android.credentials.ui.extra.BASE_RESULT";
+ /** @hide **/
+ @IntDef(prefix = {"RESULT_CODE_"}, value = {
+ RESULT_CODE_DIALOG_USER_CANCELED,
+ RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS,
+ RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
/** User intentionally canceled the dialog. */
- public static final int RESULT_CODE_DIALOG_CANCELED = 0;
- /**
- * User made a selection and the dialog finished. The user selection result is in the
- * {@code resultData}.
- */
- public static final int RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION = 1;
- /**
- * The user has acknowledged the consent page rendered for when they first used Credential
- * Manager on this device.
- */
- public static final int RESULT_CODE_CREDENTIAL_MANAGER_CONSENT_ACKNOWLEDGED = 2;
- /**
- * The user has acknowledged the consent page rendered for enabling a new provider.
- * This should only happen during the first time use. The provider info is in the
- * {@code resultData}.
- */
- public static final int RESULT_CODE_PROVIDER_ENABLED = 3;
+ public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0;
/**
* The user has consented to switching to a new default provider. The provider info is in the
* {@code resultData}.
*/
- public static final int RESULT_CODE_DEFAULT_PROVIDER_CHANGED = 4;
+ public static final int RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 1;
+ /**
+ * User made a selection and the dialog finished. The user selection result is in the
+ * {@code resultData}.
+ */
+ public static final int RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION = 2;
@NonNull
private final IBinder mRequestToken;
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index e7b8dab..49ae9e9 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -48,9 +48,9 @@
/** Type value for any request that does not require UI. */
@NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
- /** Type value for an executeGetCredential request. */
+ /** Type value for a getCredential request. */
@NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
- /** Type value for an executeCreateCredential request. */
+ /** Type value for a createCredential request. */
@NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
/** @hide */
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 04a204a..b24b30e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -470,6 +470,16 @@
* @param isAcquiredGood whether the fingerprint image was good.
*/
public void onAcquired(boolean isAcquiredGood){ }
+
+ /**
+ * Called when a pointer down event has occurred.
+ */
+ public void onPointerDown(int sensorId){ }
+
+ /**
+ * Called when a pointer up event has occurred.
+ */
+ public void onPointerUp(int sensorId){ }
}
/**
@@ -1398,7 +1408,7 @@
if (mAuthenticationCallback != null) {
mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
}
- if (mEnrollmentCallback != null) {
+ if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
}
final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
@@ -1454,17 +1464,24 @@
private void sendUdfpsPointerDown(int sensorId) {
if (mAuthenticationCallback == null) {
Slog.e(TAG, "sendUdfpsPointerDown, callback null");
- return;
+ } else {
+ mAuthenticationCallback.onUdfpsPointerDown(sensorId);
}
- mAuthenticationCallback.onUdfpsPointerDown(sensorId);
+
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onPointerDown(sensorId);
+ }
}
private void sendUdfpsPointerUp(int sensorId) {
if (mAuthenticationCallback == null) {
Slog.e(TAG, "sendUdfpsPointerUp, callback null");
- return;
+ } else {
+ mAuthenticationCallback.onUdfpsPointerUp(sensorId);
}
- mAuthenticationCallback.onUdfpsPointerUp(sensorId);
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onPointerUp(sensorId);
+ }
}
private void sendPowerPressed() {
diff --git a/core/java/android/hardware/input/HostUsiVersion.aidl b/core/java/android/hardware/input/HostUsiVersion.aidl
new file mode 100644
index 0000000..74f0ba8
--- /dev/null
+++ b/core/java/android/hardware/input/HostUsiVersion.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.hardware.input;
+
+parcelable HostUsiVersion;
diff --git a/core/java/android/hardware/input/HostUsiVersion.java b/core/java/android/hardware/input/HostUsiVersion.java
new file mode 100644
index 0000000..8f13d75
--- /dev/null
+++ b/core/java/android/hardware/input/HostUsiVersion.java
@@ -0,0 +1,204 @@
+/*
+ * 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 android.hardware.input;
+
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Provides information about the supported Universal Stylus Initiative (USI) version of the
+ * host device.
+ *
+ * This holds version information about the host device (e.g. the touchscreen/display), not
+ * the USI version of a stylus.
+ *
+ * @see InputManager#getHostUsiVersion(android.view.Display)
+ * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+ */
+@DataClass(genParcelable = true, genHiddenConstructor = true, genToString = true,
+ genEqualsHashCode = true)
+public final class HostUsiVersion implements Parcelable {
+ /**
+ * The major USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 2.
+ */
+ private final int mMajorVersion;
+
+ /**
+ * The minor USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 0.
+ */
+ private final int mMinorVersion;
+
+ /** @hide */
+ public boolean isValid() {
+ return mMajorVersion >= 0 && mMinorVersion >= 0;
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/HostUsiVersion.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new HostUsiVersion.
+ *
+ * @param majorVersion
+ * The major USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 2.
+ * @param minorVersion
+ * The minor USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 0.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public HostUsiVersion(
+ int majorVersion,
+ int minorVersion) {
+ this.mMajorVersion = majorVersion;
+ this.mMinorVersion = minorVersion;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The major USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 2.
+ */
+ @DataClass.Generated.Member
+ public int getMajorVersion() {
+ return mMajorVersion;
+ }
+
+ /**
+ * The minor USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 0.
+ */
+ @DataClass.Generated.Member
+ public int getMinorVersion() {
+ return mMinorVersion;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "HostUsiVersion { " +
+ "majorVersion = " + mMajorVersion + ", " +
+ "minorVersion = " + mMinorVersion +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(HostUsiVersion other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ HostUsiVersion that = (HostUsiVersion) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mMajorVersion == that.mMajorVersion
+ && mMinorVersion == that.mMinorVersion;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mMajorVersion;
+ _hash = 31 * _hash + mMinorVersion;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mMajorVersion);
+ dest.writeInt(mMinorVersion);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ HostUsiVersion(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int majorVersion = in.readInt();
+ int minorVersion = in.readInt();
+
+ this.mMajorVersion = majorVersion;
+ this.mMinorVersion = minorVersion;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<HostUsiVersion> CREATOR
+ = new Parcelable.Creator<HostUsiVersion>() {
+ @Override
+ public HostUsiVersion[] newArray(int size) {
+ return new HostUsiVersion[size];
+ }
+
+ @Override
+ public HostUsiVersion createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new HostUsiVersion(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1673884256908L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/hardware/input/HostUsiVersion.java",
+ inputSignatures = "private final int mMajorVersion\nprivate final int mMinorVersion\npublic boolean isValid()\nclass HostUsiVersion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index fb201cf..f27c34c 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -56,8 +56,10 @@
import android.os.VibratorManager;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
+import android.util.DisplayUtils;
import android.util.Log;
import android.util.SparseArray;
+import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
@@ -69,6 +71,7 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
@@ -99,6 +102,8 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final IInputManager mIm;
+ private final boolean mIsStylusPointerIconEnabled;
+
// Guarded by mInputDevicesLock
private final Object mInputDevicesLock = new Object();
private SparseArray<InputDevice> mInputDevices;
@@ -170,7 +175,7 @@
* The <code>android:label</code> attribute specifies a human-readable descriptive
* label to describe the keyboard layout in the user interface, such as "English (US)".
* The <code>android:keyboardLayout</code> attribute refers to a
- * <a href="http://source.android.com/tech/input/key-character-map-files.html">
+ * <a href="https://source.android.com/docs/core/interaction/input/key-character-map-files">
* key character map</a> resource that defines the keyboard layout.
* The <code>android:keyboardLocale</code> attribute specifies a comma separated list of BCP 47
* language tags depicting the locales supported by the keyboard layout. This attribute is
@@ -321,6 +326,15 @@
} catch (RemoteException ex) {
Log.w(TAG, "Could not get VelocityTracker strategy: " + ex);
}
+
+ // TODO(b/266013036): Pass a Context into InputManager constructor.
+ final Context context = ActivityThread.currentApplication();
+ if (context != null) {
+ mIsStylusPointerIconEnabled = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon);
+ } else {
+ mIsStylusPointerIconEnabled = false;
+ }
}
/**
@@ -1379,7 +1393,7 @@
* Changes the mouse pointer's icon shape into the specified id.
*
* @param iconId The id of the pointer graphic, as a value between
- * {@link PointerIcon.TYPE_ARROW} and {@link PointerIcon.TYPE_GRABBING}.
+ * {@link PointerIcon.TYPE_ARROW} and {@link PointerIcon.TYPE_HANDWRITING}.
*
* @hide
*/
@@ -1402,6 +1416,16 @@
}
/**
+ * Check if showing a {@link android.view.PointerIcon} for styluses is enabled.
+ *
+ * @return true if a pointer icon will be shown over the location of a
+ * stylus pointer, false if there is no pointer icon shown for styluses.
+ */
+ public boolean isStylusPointerIconEnabled() {
+ return mIsStylusPointerIconEnabled;
+ }
+
+ /**
* Request or release pointer capture.
* <p>
* When in capturing mode, the pointer icon disappears and all mouse events are dispatched to
@@ -1584,6 +1608,66 @@
}
}
+ /**
+ * Reports the version of the Universal Stylus Initiative (USI) protocol supported by the given
+ * display, if any.
+ *
+ * @return the USI version supported by the display, or null if the device does not support USI
+ * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+ */
+ @Nullable
+ public HostUsiVersion getHostUsiVersion(@NonNull Display display) {
+ Objects.requireNonNull(display, "display should not be null");
+
+ // Return the first valid USI version reported by any input device associated with
+ // the display.
+ synchronized (mInputDevicesLock) {
+ populateInputDevicesLocked();
+
+ for (int i = 0; i < mInputDevices.size(); i++) {
+ final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
+ if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
+ if (device.getHostUsiVersion() != null) {
+ return device.getHostUsiVersion();
+ }
+ }
+ }
+ }
+
+ // If there are no input devices that report a valid USI version, see if there is a config
+ // that specifies the USI version for the display. This is to handle cases where the USI
+ // input device is not registered by the kernel/driver all the time.
+ return findConfigUsiVersionForDisplay(display);
+ }
+
+ private HostUsiVersion findConfigUsiVersionForDisplay(@NonNull Display display) {
+ final Context context = Objects.requireNonNull(ActivityThread.currentApplication());
+ final String[] displayUniqueIds = context.getResources().getStringArray(
+ R.array.config_displayUniqueIdArray);
+ final int index;
+ if (displayUniqueIds.length == 0 && display.getDisplayId() == context.getDisplayId()) {
+ index = 0;
+ } else {
+ index = DisplayUtils.getDisplayUniqueIdConfigIndex(context.getResources(),
+ display.getUniqueId());
+ }
+
+ final String[] versions = context.getResources().getStringArray(
+ R.array.config_displayUsiVersionArray);
+ if (index < 0 || index >= versions.length) {
+ return null;
+ }
+ final String version = versions[index];
+ if (version == null || version.isEmpty()) {
+ return null;
+ }
+ final String[] majorMinor = version.split("\\.");
+ if (majorMinor.length != 2) {
+ throw new IllegalStateException("Failed to parse USI version: " + version);
+ }
+ return new HostUsiVersion(Integer.parseInt(majorMinor[0]), Integer.parseInt(majorMinor[1]));
+ }
+
private void populateInputDevicesLocked() {
if (mInputDevicesChangedListener == null) {
final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index ac23af4..01ce7b9 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -995,6 +995,51 @@
}
/**
+ * Puts the Context Hub in test mode.
+ *
+ * The purpose of this API is to make testing CHRE/Context Hub more
+ * predictable and robust. This temporarily unloads all
+ * nanoapps.
+ *
+ * Note that this API must not cause CHRE/Context Hub to behave differently
+ * in test compared to production.
+ *
+ * @return true if the enable test mode operation succeeded.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @NonNull public boolean enableTestMode() {
+ try {
+ return mService.setTestMode(true);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Puts the Context Hub out of test mode.
+ *
+ * This API will undo any previously made enableTestMode() calls.
+ * After this API is called, it should restore the state of the system
+ * to the normal/production mode before any enableTestMode() call was
+ * made. If the enableTestMode() call unloaded any nanoapps
+ * to enter test mode, it should reload those nanoapps in this API call.
+ *
+ * @return true if the disable operation succeeded.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @NonNull public boolean disableTestMode() {
+ try {
+ return mService.setTestMode(false);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Unregister a callback for receive messages from the context hub.
*
* @see Callback
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 490267f..11f30461 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -113,4 +113,8 @@
// Queries for a list of preloaded nanoapps
@EnforcePermission("ACCESS_CONTEXT_HUB")
long[] getPreloadedNanoAppIds(in ContextHubInfo hubInfo);
+
+ // Enables or disables test mode
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ boolean setTestMode(in boolean enable);
}
diff --git a/core/java/android/nfc/BeamShareData.aidl b/core/java/android/nfc/BeamShareData.aidl
new file mode 100644
index 0000000..a47e240
--- /dev/null
+++ b/core/java/android/nfc/BeamShareData.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.nfc;
+
+parcelable BeamShareData;
diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java
new file mode 100644
index 0000000..6a40f98
--- /dev/null
+++ b/core/java/android/nfc/BeamShareData.java
@@ -0,0 +1,67 @@
+package android.nfc;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+/**
+ * Class to IPC data to be shared over Android Beam.
+ * Allows bundling NdefMessage, Uris and flags in a single
+ * IPC call. This is important as we want to reduce the
+ * amount of IPC calls at "touch time".
+ * @hide
+ */
+public final class BeamShareData implements Parcelable {
+ public final NdefMessage ndefMessage;
+ public final Uri[] uris;
+ public final UserHandle userHandle;
+ public final int flags;
+
+ public BeamShareData(NdefMessage msg, Uri[] uris, UserHandle userHandle, int flags) {
+ this.ndefMessage = msg;
+ this.uris = uris;
+ this.userHandle = userHandle;
+ this.flags = flags;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ int urisLength = (uris != null) ? uris.length : 0;
+ dest.writeParcelable(ndefMessage, 0);
+ dest.writeInt(urisLength);
+ if (urisLength > 0) {
+ dest.writeTypedArray(uris, 0);
+ }
+ dest.writeParcelable(userHandle, 0);
+ dest.writeInt(this.flags);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<BeamShareData> CREATOR =
+ new Parcelable.Creator<BeamShareData>() {
+ @Override
+ public BeamShareData createFromParcel(Parcel source) {
+ Uri[] uris = null;
+ NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader(), android.nfc.NdefMessage.class);
+ int numUris = source.readInt();
+ if (numUris > 0) {
+ uris = new Uri[numUris];
+ source.readTypedArray(uris, Uri.CREATOR);
+ }
+ UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
+ int flags = source.readInt();
+
+ return new BeamShareData(msg, uris, userHandle, flags);
+ }
+
+ @Override
+ public BeamShareData[] newArray(int size) {
+ return new BeamShareData[size];
+ }
+ };
+}
diff --git a/core/java/android/nfc/IAppCallback.aidl b/core/java/android/nfc/IAppCallback.aidl
index b06bf06..133146d 100644
--- a/core/java/android/nfc/IAppCallback.aidl
+++ b/core/java/android/nfc/IAppCallback.aidl
@@ -16,6 +16,7 @@
package android.nfc;
+import android.nfc.BeamShareData;
import android.nfc.Tag;
/**
@@ -23,5 +24,7 @@
*/
interface IAppCallback
{
+ BeamShareData createBeamShareData(byte peerLlcpVersion);
+ oneway void onNdefPushComplete(byte peerLlcpVersion);
oneway void onTagDiscovered(in Tag tag);
}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index a6d8caf..8a30ef4 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.content.IntentFilter;
+import android.nfc.BeamShareData;
import android.nfc.NdefMessage;
import android.nfc.Tag;
import android.nfc.TechListParcel;
@@ -46,18 +47,24 @@
int getState();
boolean disable(boolean saveState);
boolean enable();
+ boolean enableNdefPush();
+ boolean disableNdefPush();
+ boolean isNdefPushEnabled();
void pausePolling(int timeoutInMs);
void resumePolling();
void setForegroundDispatch(in PendingIntent intent,
in IntentFilter[] filters, in TechListParcel techLists);
void setAppCallback(in IAppCallback callback);
+ oneway void invokeBeam();
+ oneway void invokeBeamInternal(in BeamShareData shareData);
boolean ignore(int nativeHandle, int debounceMs, ITagRemovedCallback callback);
void dispatch(in Tag tag);
void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
+ void setP2pModes(int initatorModes, int targetModes);
void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 8d75cac..911aaf3 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -19,6 +19,9 @@
import android.app.Activity;
import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.net.Uri;
import android.nfc.NfcAdapter.ReaderCallback;
import android.os.Binder;
import android.os.Bundle;
@@ -107,8 +110,14 @@
class NfcActivityState {
boolean resumed = false;
Activity activity;
- NfcAdapter.ReaderCallback readerCallback = null;
+ NdefMessage ndefMessage = null; // static NDEF message
+ NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
+ NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
+ NfcAdapter.CreateBeamUrisCallback uriCallback = null;
+ Uri[] uris = null;
+ int flags = 0;
int readerModeFlags = 0;
+ NfcAdapter.ReaderCallback readerCallback = null;
Bundle readerModeExtras = null;
Binder token;
@@ -128,16 +137,24 @@
unregisterApplication(activity.getApplication());
resumed = false;
activity = null;
- readerCallback = null;
+ ndefMessage = null;
+ ndefMessageCallback = null;
+ onNdefPushCompleteCallback = null;
+ uriCallback = null;
+ uris = null;
readerModeFlags = 0;
- readerModeExtras = null;
token = null;
}
@Override
public String toString() {
- StringBuilder s = new StringBuilder("[");
- s.append(readerCallback);
- s.append("]");
+ StringBuilder s = new StringBuilder("[").append(" ");
+ s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
+ s.append(uriCallback).append(" ");
+ if (uris != null) {
+ for (Uri uri : uris) {
+ s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
+ }
+ }
return s.toString();
}
}
@@ -228,6 +245,92 @@
}
}
+ public void setNdefPushContentUri(Activity activity, Uri[] uris) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.uris = uris;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
+ requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
+ }
+ }
+
+
+ public void setNdefPushContentUriCallback(Activity activity,
+ NfcAdapter.CreateBeamUrisCallback callback) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.uriCallback = callback;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
+ requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
+ }
+ }
+
+ public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.ndefMessage = message;
+ state.flags = flags;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
+ requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
+ }
+ }
+
+ public void setNdefPushMessageCallback(Activity activity,
+ NfcAdapter.CreateNdefMessageCallback callback, int flags) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.ndefMessageCallback = callback;
+ state.flags = flags;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
+ requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
+ }
+ }
+
+ public void setOnNdefPushCompleteCallback(Activity activity,
+ NfcAdapter.OnNdefPushCompleteCallback callback) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.onNdefPushCompleteCallback = callback;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ // requestNfcServiceCallback() verifies permission also
+ requestNfcServiceCallback();
+ } else {
+ // Crash API calls early in case NFC permission is missing
+ verifyNfcPermission();
+ }
+ }
+
/**
* Request or unrequest NFC service callbacks.
* Makes IPC call - do not hold lock.
@@ -248,6 +351,86 @@
}
}
+ /** Callback from NFC service, usually on binder thread */
+ @Override
+ public BeamShareData createBeamShareData(byte peerLlcpVersion) {
+ NfcAdapter.CreateNdefMessageCallback ndefCallback;
+ NfcAdapter.CreateBeamUrisCallback urisCallback;
+ NdefMessage message;
+ Activity activity;
+ Uri[] uris;
+ int flags;
+ NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findResumedActivityState();
+ if (state == null) return null;
+
+ ndefCallback = state.ndefMessageCallback;
+ urisCallback = state.uriCallback;
+ message = state.ndefMessage;
+ uris = state.uris;
+ flags = state.flags;
+ activity = state.activity;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ // Make callbacks without lock
+ if (ndefCallback != null) {
+ message = ndefCallback.createNdefMessage(event);
+ }
+ if (urisCallback != null) {
+ uris = urisCallback.createBeamUris(event);
+ if (uris != null) {
+ ArrayList<Uri> validUris = new ArrayList<Uri>();
+ for (Uri uri : uris) {
+ if (uri == null) {
+ Log.e(TAG, "Uri not allowed to be null.");
+ continue;
+ }
+ String scheme = uri.getScheme();
+ if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
+ !scheme.equalsIgnoreCase("content"))) {
+ Log.e(TAG, "Uri needs to have " +
+ "either scheme file or scheme content");
+ continue;
+ }
+ uri = ContentProvider.maybeAddUserId(uri, activity.getUserId());
+ validUris.add(uri);
+ }
+
+ uris = validUris.toArray(new Uri[validUris.size()]);
+ }
+ }
+ if (uris != null && uris.length > 0) {
+ for (Uri uri : uris) {
+ // Grant the NFC process permission to read these URIs
+ activity.grantUriPermission("com.android.nfc", uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return new BeamShareData(message, uris, activity.getUser(), flags);
+ }
+
+ /** Callback from NFC service, usually on binder thread */
+ @Override
+ public void onNdefPushComplete(byte peerLlcpVersion) {
+ NfcAdapter.OnNdefPushCompleteCallback callback;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = findResumedActivityState();
+ if (state == null) return;
+
+ callback = state.onNdefPushCompleteCallback;
+ }
+ NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
+ // Make callback without lock
+ if (callback != null) {
+ callback.onNdefPushComplete(event);
+ }
+ }
+
@Override
public void onTagDiscovered(Tag tag) throws RemoteException {
NfcAdapter.ReaderCallback callback;
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index d947b48..a980158 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -345,10 +345,7 @@
*/
public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
- /**
- * @hide
- * @removed
- */
+ /** @hide */
@SystemApi
public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1;
@@ -423,6 +420,7 @@
// Guarded by NfcAdapter.class
static boolean sIsInitialized = false;
static boolean sHasNfcFeature;
+ static boolean sHasBeamFeature;
// Final after first constructor, except for
// attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -486,7 +484,7 @@
* A callback to be invoked when the system successfully delivers your {@link NdefMessage}
* to another device.
* @see #setOnNdefPushCompleteCallback
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -512,7 +510,7 @@
* content currently visible to the user. Alternatively, you can call {@link
* #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
* same data.
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -542,7 +540,7 @@
/**
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -574,6 +572,26 @@
}
/**
+ * Helper to check if this device has FEATURE_NFC_BEAM, but without using
+ * a context.
+ * Equivalent to
+ * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
+ */
+ private static boolean hasBeamFeature() {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
+ return false;
+ }
+ try {
+ return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
+ return false;
+ }
+ }
+
+ /**
* Helper to check if this device has FEATURE_NFC, but without using
* a context.
* Equivalent to
@@ -652,6 +670,7 @@
public static synchronized NfcAdapter getNfcAdapter(Context context) {
if (!sIsInitialized) {
sHasNfcFeature = hasNfcFeature();
+ sHasBeamFeature = hasBeamFeature();
boolean hasHceFeature = hasNfcHceFeature();
/* is this device meant to have NFC */
if (!sHasNfcFeature && !hasHceFeature) {
@@ -1144,7 +1163,7 @@
* @param uris an array of Uri(s) to push over Android Beam
* @param activity activity for which the Uri(s) will be pushed
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -1153,7 +1172,26 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return;
+ }
}
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ if (uris != null) {
+ for (Uri uri : uris) {
+ if (uri == null) throw new NullPointerException("Uri not " +
+ "allowed to be null");
+ String scheme = uri.getScheme();
+ if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
+ !scheme.equalsIgnoreCase("content"))) {
+ throw new IllegalArgumentException("URI needs to have " +
+ "either scheme file or scheme content");
+ }
+ }
+ }
+ mNfcActivityManager.setNdefPushContentUri(activity, uris);
}
/**
@@ -1213,7 +1251,7 @@
* @param callback callback, or null to disable
* @param activity activity for which the Uri(s) will be pushed
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -1222,7 +1260,14 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return;
+ }
}
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushContentUriCallback(activity, callback);
}
/**
@@ -1296,7 +1341,7 @@
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -1306,12 +1351,36 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return;
+ }
+ }
+ int targetSdkVersion = getSdkVersion();
+ try {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushMessage(activity, message, 0);
+ for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
+ mNfcActivityManager.setNdefPushMessage(a, message, 0);
+ }
+ } catch (IllegalStateException e) {
+ if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ // Less strict on old applications - just log the error
+ Log.e(TAG, "Cannot call API with Activity that has already " +
+ "been destroyed", e);
+ } else {
+ // Prevent new applications from making this mistake, re-throw
+ throw(e);
+ }
}
}
/**
* @hide
- * @removed
*/
@SystemApi
public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
@@ -1320,6 +1389,10 @@
throw new UnsupportedOperationException();
}
}
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushMessage(activity, message, flags);
}
/**
@@ -1387,7 +1460,7 @@
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -1397,7 +1470,44 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return;
+ }
}
+ int targetSdkVersion = getSdkVersion();
+ try {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushMessageCallback(activity, callback, 0);
+ for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
+ mNfcActivityManager.setNdefPushMessageCallback(a, callback, 0);
+ }
+ } catch (IllegalStateException e) {
+ if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ // Less strict on old applications - just log the error
+ Log.e(TAG, "Cannot call API with Activity that has already " +
+ "been destroyed", e);
+ } else {
+ // Prevent new applications from making this mistake, re-throw
+ throw(e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
+ int flags) {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushMessageCallback(activity, callback, flags);
}
/**
@@ -1437,7 +1547,7 @@
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -1447,6 +1557,31 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return;
+ }
+ }
+ int targetSdkVersion = getSdkVersion();
+ try {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callback);
+ for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
+ mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
+ }
+ } catch (IllegalStateException e) {
+ if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ // Less strict on old applications - just log the error
+ Log.e(TAG, "Cannot call API with Activity that has already " +
+ "been destroyed", e);
+ } else {
+ // Prevent new applications from making this mistake, re-throw
+ throw(e);
+ }
}
}
@@ -1629,7 +1764,7 @@
* @param activity the current foreground Activity that has registered data to share
* @return whether the Beam animation was successfully invoked
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
@@ -1638,8 +1773,37 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return false;
+ }
}
- return false;
+ if (activity == null) {
+ throw new NullPointerException("activity may not be null.");
+ }
+ enforceResumed(activity);
+ try {
+ sService.invokeBeam();
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "invokeBeam: NFC process has died.");
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean invokeBeam(BeamShareData shareData) {
+ try {
+ Log.e(TAG, "invokeBeamInternal()");
+ sService.invokeBeamInternal(shareData);
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "invokeBeam: NFC process has died.");
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
}
/**
@@ -1665,9 +1829,9 @@
*
* @param activity foreground activity
* @param message a NDEF Message to push over NFC
- * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
- * @removed this feature is removed. File sharing can work using other technology like
- * Bluetooth.
+ * @throws IllegalStateException if the activity is not currently in the foreground
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @deprecated use {@link #setNdefPushMessage} instead
*/
@Deprecated
public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
@@ -1675,7 +1839,15 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return;
+ }
}
+ if (activity == null || message == null) {
+ throw new NullPointerException();
+ }
+ enforceResumed(activity);
+ mNfcActivityManager.setNdefPushMessage(activity, message, 0);
}
/**
@@ -1694,9 +1866,9 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param activity the Foreground activity
- * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
- * @removed this feature is removed. File sharing can work using other technology like
- * Bluetooth.
+ * @throws IllegalStateException if the Activity has already been paused
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @deprecated use {@link #setNdefPushMessage} instead
*/
@Deprecated
public void disableForegroundNdefPush(Activity activity) {
@@ -1704,7 +1876,17 @@
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return;
+ }
}
+ if (activity == null) {
+ throw new NullPointerException();
+ }
+ enforceResumed(activity);
+ mNfcActivityManager.setNdefPushMessage(activity, null, 0);
+ mNfcActivityManager.setNdefPushMessageCallback(activity, null, 0);
+ mNfcActivityManager.setOnNdefPushCompleteCallback(activity, null);
}
/**
@@ -1829,24 +2011,40 @@
* Enable NDEF Push feature.
* <p>This API is for the Settings application.
* @hide
- * @removed
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean enableNdefPush() {
- return false;
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.enableNdefPush();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
}
/**
* Disable NDEF Push feature.
* <p>This API is for the Settings application.
* @hide
- * @removed
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean disableNdefPush() {
- return false;
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ try {
+ return sService.disableNdefPush();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
}
/**
@@ -1872,17 +2070,26 @@
* @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
* @return true if NDEF Push feature is enabled
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @removed this feature is removed. File sharing can work using other technology like
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
* Bluetooth.
*/
@java.lang.Deprecated
+
public boolean isNdefPushEnabled() {
synchronized (NfcAdapter.class) {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
+ if (!sHasBeamFeature) {
+ return false;
+ }
}
- return false;
+ try {
+ return sService.isNdefPushEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
}
/**
@@ -1973,6 +2180,17 @@
}
/**
+ * @hide
+ */
+ public void setP2pModes(int initiatorModes, int targetModes) {
+ try {
+ sService.setP2pModes(initiatorModes, targetModes);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
* Registers a new NFC unlock handler with the NFC service.
*
* <p />NFC unlock handlers are intended to unlock the keyguard in the presence of a trusted
diff --git a/core/java/android/nfc/OWNERS b/core/java/android/nfc/OWNERS
index 6aaf039..9a2e446 100644
--- a/core/java/android/nfc/OWNERS
+++ b/core/java/android/nfc/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 48448
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
diff --git a/core/java/android/nfc/cardemulation/OWNERS b/core/java/android/nfc/cardemulation/OWNERS
index 6aaf039..9a2e446 100644
--- a/core/java/android/nfc/cardemulation/OWNERS
+++ b/core/java/android/nfc/cardemulation/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 48448
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
diff --git a/core/java/android/nfc/dta/OWNERS b/core/java/android/nfc/dta/OWNERS
index 6aaf039..9a2e446 100644
--- a/core/java/android/nfc/dta/OWNERS
+++ b/core/java/android/nfc/dta/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 48448
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
diff --git a/core/java/android/nfc/tech/OWNERS b/core/java/android/nfc/tech/OWNERS
index 6aaf039..9a2e446 100644
--- a/core/java/android/nfc/tech/OWNERS
+++ b/core/java/android/nfc/tech/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 48448
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 32773a0..249f486 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -754,9 +754,12 @@
* PackageManager.setComponentEnabledSetting} will now throw an
* IllegalArgumentException if the given component class name does not
* exist in the application's manifest.
- * <li> {@code NfcAdapter.setNdefPushMessage},
- * {@code NfcAdapter.setNdefPushMessageCallback} and
- * {@code NfcAdapter.setOnNdefPushCompleteCallback} will throw
+ * <li> {@link android.nfc.NfcAdapter#setNdefPushMessage
+ * NfcAdapter.setNdefPushMessage},
+ * {@link android.nfc.NfcAdapter#setNdefPushMessageCallback
+ * NfcAdapter.setNdefPushMessageCallback} and
+ * {@link android.nfc.NfcAdapter#setOnNdefPushCompleteCallback
+ * NfcAdapter.setOnNdefPushCompleteCallback} will throw
* IllegalStateException if called after the Activity has been destroyed.
* <li> Accessibility services must require the new
* {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission or
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index d1d3315..797730b 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -116,8 +116,6 @@
boolean someUserHasSeedAccount(in String accountName, in String accountType);
boolean someUserHasAccount(in String accountName, in String accountType);
String getProfileType(int userId);
- boolean isMediaSharedWithParent(int userId);
- boolean isCredentialSharableWithParent(int userId);
boolean isDemoUser(int userId);
boolean isPreCreated(int userId);
UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags,
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 62d8fb2..6582605 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -65,7 +65,6 @@
import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
-import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.FrameworkStatsLog;
import java.io.IOException;
@@ -2111,17 +2110,6 @@
}
/**
- * @hide
- * @return Whether the device is running with split system user. It means the system user and
- * primary user are two separate users. Previously system user and primary user are combined as
- * a single owner user. see @link {android.os.UserHandle#USER_OWNER}
- */
- @TestApi
- public static boolean isSplitSystemUser() {
- return RoSystemProperties.FW_SYSTEM_USER_SPLIT;
- }
-
- /**
* @return Whether guest user is always ephemeral
* @hide
*/
@@ -3260,7 +3248,10 @@
* @param userHandle the user handle of the user whose information is being requested.
* @return a UserProperties object for a specific user.
* @throws IllegalArgumentException if {@code userHandle} doesn't correspond to an existing user
+ *
+ * @hide
*/
+ @SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.QUERY_USERS,
@@ -5177,19 +5168,25 @@
* Returns false for any other type of user.
*
* @return true if the user shares media with its parent user, false otherwise.
+ *
+ * @deprecated use {@link #getUserProperties(UserHandle)} with
+ * {@link UserProperties#getIsMediaSharedWithParent()} instead.
* @hide
*/
@SystemApi
+ @Deprecated
@UserHandleAware(
requiresAnyOfPermissionsIfNotCallerProfileGroup = {
Manifest.permission.MANAGE_USERS,
+ Manifest.permission.QUERY_USERS,
Manifest.permission.INTERACT_ACROSS_USERS})
@SuppressAutoDoc
public boolean isMediaSharedWithParent() {
try {
- return mService.isMediaSharedWithParent(mUserId);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
+ return getUserProperties(UserHandle.of(mUserId)).getIsMediaSharedWithParent();
+ } catch (IllegalArgumentException e) {
+ // If the user doesn't exist, return false (for historical reasons)
+ return false;
}
}
@@ -5199,19 +5196,24 @@
* This API only works for {@link UserManager#isProfile() profiles}
* and will always return false for any other user type.
*
+ * @deprecated use {@link #getUserProperties(UserHandle)} with
+ * {@link UserProperties#getIsMediaSharedWithParent()} instead.
* @hide
*/
@SystemApi
+ @Deprecated
@UserHandleAware(
requiresAnyOfPermissionsIfNotCallerProfileGroup = {
Manifest.permission.MANAGE_USERS,
+ Manifest.permission.QUERY_USERS,
Manifest.permission.INTERACT_ACROSS_USERS})
@SuppressAutoDoc
public boolean isCredentialSharableWithParent() {
try {
- return mService.isCredentialSharableWithParent(mUserId);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
+ return getUserProperties(UserHandle.of(mUserId)).getIsCredentialSharableWithParent();
+ } catch (IllegalArgumentException e) {
+ // If the user doesn't exist, return false (for historical reasons)
+ return false;
}
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 363d035..bfc5afe 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -6422,7 +6422,7 @@
* for {@link #TYPE_CUSTOM}.
*/
public static final CharSequence getTypeLabel(Resources res, int type,
- CharSequence label) {
+ @Nullable CharSequence label) {
if ((type == TYPE_CUSTOM || type == TYPE_ASSISTANT) && !TextUtils.isEmpty(label)) {
return label;
} else {
@@ -6634,7 +6634,7 @@
* for {@link #TYPE_CUSTOM}.
*/
public static final CharSequence getTypeLabel(Resources res, int type,
- CharSequence label) {
+ @Nullable CharSequence label) {
if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
return label;
} else {
@@ -6842,7 +6842,7 @@
* for {@link #TYPE_CUSTOM}.
*/
public static final CharSequence getTypeLabel(Resources res, int type,
- CharSequence label) {
+ @Nullable CharSequence label) {
if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
return label;
} else {
@@ -7003,7 +7003,7 @@
* for {@link #TYPE_CUSTOM}.
*/
public static final CharSequence getTypeLabel(Resources res, int type,
- CharSequence label) {
+ @Nullable CharSequence label) {
if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
return label;
} else {
@@ -7210,7 +7210,7 @@
* for {@link #TYPE_CUSTOM}.
*/
public static final CharSequence getTypeLabel(Resources res, int type,
- CharSequence label) {
+ @Nullable CharSequence label) {
if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
return label;
} else {
@@ -7337,7 +7337,7 @@
* for {@link #TYPE_CUSTOM}.
*/
public static final CharSequence getTypeLabel(Resources res, int type,
- CharSequence label) {
+ @Nullable CharSequence label) {
if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
return label;
} else {
@@ -7433,7 +7433,7 @@
* for {@link #TYPE_CUSTOM}.
*/
public static final CharSequence getTypeLabel(Resources res, int type,
- CharSequence label) {
+ @Nullable CharSequence label) {
if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
return label;
} else {
@@ -7762,7 +7762,7 @@
* for {@link #TYPE_CUSTOM}.
*/
public static final CharSequence getTypeLabel(Resources res, int type,
- CharSequence label) {
+ @Nullable CharSequence label) {
if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) {
return label;
} else {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7a6f0bc..5b05f21 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1060,8 +1060,13 @@
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
- * Input: Nothing.
- * <p>
+ * Input: The optional {@code #EXTRA_EXPLICIT_LOCALES} with language tags that contains locales
+ * to limit available locales. This is only supported when device is under demo mode.
+ * If intent does not contain this extra, it will show system supported locale list.
+ * <br/>
+ * If {@code #EXTRA_EXPLICIT_LOCALES} contain a unsupported locale, it will still show this
+ * locale on list, but may not be supported by the devcie.
+ *
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -1069,6 +1074,18 @@
"android.settings.LOCALE_SETTINGS";
/**
+ * Activity Extra: Show explicit locales in launched locale picker activity.
+ *
+ * This can be passed as an extra field in an Activity Intent with one or more language tags
+ * as a {@link LocaleList}. This must be passed as an extra field to the
+ * {@link #ACTION_LOCALE_SETTINGS}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_EXPLICIT_LOCALES =
+ "android.provider.extra.EXPLICIT_LOCALES";
+
+ /**
* Activity Action: Show settings to allow configuration of per application locale.
* <p>
* Input: The Intent's data URI can specify the application package name to directly invoke the
@@ -1694,6 +1711,7 @@
* Input: Nothing.
* <p>
* Output: Nothing
+ * @see android.nfc.NfcAdapter#isNdefPushEnabled()
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_NFCSHARING_SETTINGS =
@@ -17984,6 +18002,28 @@
public static final String OEM_SETUP_VERSION = "oem_setup_version";
/**
+ * The key to indicate to Setup Wizard if OEM setup is completed in Wear Services.
+ * @hide
+ */
+ public static final String OEM_SETUP_COMPLETED_STATUS = "oem_setup_completed_status";
+
+ /**
+ * Constant provided to Setup Wizard to inform about failure of OEM setup in Wear
+ * Services. The value should be provided with setting name {@link
+ * #OEM_SETUP_COMPLETED_STATUS}.
+ * @hide
+ */
+ public static final int OEM_SETUP_COMPLETED_FAILURE = 0;
+
+ /**
+ * Constant provided to Setup Wizard to inform about successful completion of OEM setup
+ * in Wear Services. The value should be provided with setting name {@link
+ * #OEM_SETUP_COMPLETED_STATUS}.
+ * @hide
+ */
+ public static final int OEM_SETUP_COMPLETED_SUCCESS = 1;
+
+ /**
* Controls the gestures feature.
* @hide
*/
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 979c5a8..6896e02 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4847,6 +4847,14 @@
*/
public static final String COLUMN_USER_HANDLE = "user_handle";
+ /**
+ * TelephonyProvider column name for satellite enabled.
+ * By default, it's disabled.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SATELLITE_ENABLED = "satellite_enabled";
+
/** All columns in {@link SimInfo} table. */
private static final List<String> ALL_COLUMNS = List.of(
COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -4915,7 +4923,8 @@
COLUMN_PORT_INDEX,
COLUMN_USAGE_SETTING,
COLUMN_TP_MESSAGE_REF,
- COLUMN_USER_HANDLE
+ COLUMN_USER_HANDLE,
+ COLUMN_SATELLITE_ENABLED
);
/**
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index a389223..ff14404 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -70,7 +70,7 @@
@Override
public void onDestroy() {
- if (mCallback != null) {
+ if (mCallback != null && !isFinishing()) {
mCallback.onActivityDestroyed();
}
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 6e8198b..bf5b970 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -51,6 +51,11 @@
}
@Override
+ public void endDream() {
+ onEndDream();
+ }
+
+ @Override
public void wakeUp() {
onWakeUp(() -> {
try {
@@ -83,13 +88,22 @@
/**
* This method is overridden by implementations to handle when the dream has been requested
- * to wakeup. This allows any overlay animations to run.
+ * to wakeup. This allows any overlay animations to run. By default, the method will invoke
+ * the callback immediately.
*
* @param onCompleteCallback The callback to trigger to notify the dream service that the
* overlay has completed waking up.
* @hide
*/
public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+ onCompleteCallback.run();
+ }
+
+ /**
+ * This method is overridden by implementations to handle when the dream has ended. There may
+ * be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}.
+ */
+ public void onEndDream() {
}
/**
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 8b9852a..d378886 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -297,14 +297,20 @@
}
public void addConsumer(Consumer<IDreamOverlay> consumer) {
- mConsumers.add(consumer);
- if (mOverlay != null) {
- consumer.accept(mOverlay);
- }
+ execute(() -> {
+ mConsumers.add(consumer);
+ if (mOverlay != null) {
+ consumer.accept(mOverlay);
+ }
+ });
}
public void removeConsumer(Consumer<IDreamOverlay> consumer) {
- mConsumers.remove(consumer);
+ execute(() -> mConsumers.remove(consumer));
+ }
+
+ public void clearConsumers() {
+ execute(() -> mConsumers.clear());
}
}
@@ -1383,6 +1389,17 @@
@Override
public void onViewDetachedFromWindow(View v) {
+ if (mOverlayConnection != null) {
+ mOverlayConnection.addConsumer(overlay -> {
+ try {
+ overlay.endDream();
+ } catch (RemoteException e) {
+ Log.e(mTag, "could not inform overlay of dream end:" + e);
+ }
+ });
+ mOverlayConnection.clearConsumers();
+ }
+
if (mActivity == null || !mActivity.isChangingConfigurations()) {
// Only stop the dream if the view is not detached by relaunching
// activity for configuration changes. It is important to also clear
@@ -1391,9 +1408,6 @@
mActivity = null;
finish();
}
- if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
- mOverlayConnection.removeConsumer(mDreamStartOverlayConsumer);
- }
}
});
}
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 7aeceb2c..0e4bd3b 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -41,4 +41,7 @@
/** Called when the dream is waking, to do any exit animations */
void wakeUp();
+
+ /** Called when the dream has ended. */
+ void endDream();
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index df739e3..ca4716b 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -95,20 +95,6 @@
public static final String SERVICE_META_DATA = "android.voice_interaction";
/**
- * Bundle key used to specify the id when the system prepares to show session. It increases for
- * each request.
- * <p>
- * Type: int
- * </p>
- * @see #showSession(Bundle, int)
- * @see #onPrepareToShowSession(Bundle, int)
- * @see #onShowSessionFailed(Bundle)
- * @see VoiceInteractionSession#onShow(Bundle, int)
- * @see VoiceInteractionSession#show(Bundle, int)
- */
- public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
-
- /**
* For apps targeting Build.VERSION_CODES.TRAMISU and above, implementors of this
* service can create multiple AlwaysOnHotwordDetector instances in parallel. They will
* also e ale to create a single SoftwareHotwordDetector in parallel with any other
@@ -219,7 +205,7 @@
* bind the session service.
*
* @param args The arguments that were supplied to {@link #showSession(Bundle, int)}.
- * It always includes {@link #KEY_SHOW_SESSION_ID}.
+ * It always includes {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}.
* @param flags The show flags originally provided to {@link #showSession(Bundle, int)}.
* @see #showSession(Bundle, int)
* @see #onShowSessionFailed(Bundle)
@@ -233,7 +219,7 @@
* Called when the show session failed. E.g. When the system bound the session service failed.
*
* @param args Additional info about the show session attempt that failed. For now, includes
- * {@link #KEY_SHOW_SESSION_ID}.
+ * {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}.
* @see #showSession(Bundle, int)
* @see #onPrepareToShowSession(Bundle, int)
* @see VoiceInteractionSession#onShow(Bundle, int)
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index d55fede..0d51395 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -169,6 +169,20 @@
@Retention(RetentionPolicy.SOURCE)
public @interface VoiceInteractionActivityEventType{}
+ /**
+ * Bundle key used to specify the id when the system prepares to show session. It increases for
+ * each request.
+ * <p>
+ * Type: int
+ * </p>
+ * @see VoiceInteractionService#showSession(Bundle, int)
+ * @see VoiceInteractionService#onPrepareToShowSession(Bundle, int)
+ * @see VoiceInteractionService#onShowSessionFailed(Bundle)
+ * @see #onShow(Bundle, int)
+ * @see #show(Bundle, int)
+ */
+ public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
+
final Context mContext;
final HandlerCaller mHandlerCaller;
@@ -1763,7 +1777,7 @@
* @param args The arguments that were supplied to
* {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
* Some example keys include : "invocation_type", "invocation_phone_state",
- * {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, "invocation_time_ms",
+ * {@link #KEY_SHOW_SESSION_ID}, "invocation_time_ms",
* Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
* in milliseconds of the KeyEvent that triggered Assistant and
* Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java
new file mode 100644
index 0000000..fbdaa3d
--- /dev/null
+++ b/core/java/android/text/method/InsertModeTransformationMethod.java
@@ -0,0 +1,440 @@
+/*
+ * 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 android.text.method;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.style.ReplacementSpan;
+import android.util.DisplayMetrics;
+import android.util.MathUtils;
+import android.util.TypedValue;
+import android.view.View;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+/**
+ * The transformation method used by handwriting insert mode.
+ * This transformation will insert a placeholder string to the original text at the given
+ * offset. And it also provides a highlight range for the newly inserted text and the placeholder
+ * text.
+ *
+ * For example,
+ * original text: "Hello world"
+ * insert mode is started at index: 5,
+ * placeholder text: "\n\n"
+ * The transformed text will be: "Hello\n\n world", and the highlight range will be [5, 7)
+ * including the inserted placeholder text.
+ *
+ * If " abc" is inserted to the original text at index 5,
+ * the new original text: "Hello abc world"
+ * the new transformed text: "hello abc\n\n world", and the highlight range will be [5, 11).
+ * @hide
+ */
+public class InsertModeTransformationMethod implements TransformationMethod, TextWatcher {
+ /** The start offset of the highlight range in the original text, inclusive. */
+ private int mStart;
+ /**
+ * The end offset of the highlight range in the original text, exclusive. The placeholder text
+ * is also inserted at this index.
+ */
+ private int mEnd;
+ /** The transformation method that's already set on the {@link android.widget.TextView}. */
+ private final TransformationMethod mOldTransformationMethod;
+ /** Whether the {@link android.widget.TextView} is single-lined. */
+ private final boolean mSingleLine;
+
+ /**
+ * @param offset the original offset to start the insert mode. It must be in the range from 0
+ * to the length of the transformed text.
+ * @param singleLine whether the text is single line.
+ * @param oldTransformationMethod the old transformation method at the
+ * {@link android.widget.TextView}. If it's not null, this {@link TransformationMethod} will
+ * first call {@link TransformationMethod#getTransformation(CharSequence, View)} on the old one,
+ * and then do the transformation for the insert mode.
+ *
+ */
+ public InsertModeTransformationMethod(@IntRange(from = 0) int offset, boolean singleLine,
+ @NonNull TransformationMethod oldTransformationMethod) {
+ mStart = offset;
+ mEnd = offset;
+ mSingleLine = singleLine;
+ mOldTransformationMethod = oldTransformationMethod;
+ }
+
+ public TransformationMethod getOldTransformationMethod() {
+ return mOldTransformationMethod;
+ }
+
+ private CharSequence getPlaceholderText(View view) {
+ if (!mSingleLine) {
+ return "\n\n";
+ }
+ final SpannableString singleLinePlaceholder = new SpannableString("\uFFFD");
+ final DisplayMetrics displayMetrics = view.getResources().getDisplayMetrics();
+ final int widthPx = (int) Math.ceil(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 108, displayMetrics));
+
+ singleLinePlaceholder.setSpan(new SingleLinePlaceholderSpan(widthPx), 0, 1,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return singleLinePlaceholder;
+ }
+
+ @Override
+ public CharSequence getTransformation(CharSequence source, View view) {
+ final CharSequence charSequence;
+ if (mOldTransformationMethod != null) {
+ charSequence = mOldTransformationMethod.getTransformation(source, view);
+ if (source instanceof Spannable) {
+ final Spannable spannable = (Spannable) source;
+ spannable.setSpan(mOldTransformationMethod, 0, spannable.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ } else {
+ charSequence = source;
+ }
+
+ final CharSequence placeholderText = getPlaceholderText(view);
+ return new TransformedText(charSequence, placeholderText);
+ }
+
+ @Override
+ public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction,
+ Rect previouslyFocusedRect) {
+ if (mOldTransformationMethod != null) {
+ mOldTransformationMethod.onFocusChanged(view, sourceText, focused, direction,
+ previouslyFocusedRect);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // The text change is after the offset where placeholder is inserted, return.
+ if (start > mEnd) return;
+ final int diff = count - before;
+
+ // Note: If start == mStart and before == 0, the change is also considered after the
+ // highlight start. It won't modify the mStart in this case.
+ if (start < mStart) {
+ if (start + before <= mStart) {
+ // The text change is before the highlight start, move the highlight start.
+ mStart += diff;
+ } else {
+ // The text change covers the highlight start. Extend the highlight start to the
+ // change start. This should be a rare case.
+ mStart = start;
+ }
+ }
+
+ if (start + before <= mEnd) {
+ // The text change is before the highlight end, move the highlight end.
+ mEnd += diff;
+ } else if (start < mEnd) {
+ // The text change covers the highlight end. Extend the highlight end to the
+ // change end. This should be a rare case.
+ mEnd = start + count;
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) { }
+
+ /**
+ * The transformed text returned by the {@link InsertModeTransformationMethod}.
+ */
+ public class TransformedText implements OffsetMapping, Spanned {
+ private final CharSequence mOriginal;
+ private final CharSequence mPlaceholder;
+ private final Spanned mSpannedOriginal;
+ private final Spanned mSpannedPlaceholder;
+
+ TransformedText(CharSequence original, CharSequence placeholder) {
+ mOriginal = original;
+ if (original instanceof Spanned) {
+ mSpannedOriginal = (Spanned) original;
+ } else {
+ mSpannedOriginal = null;
+ }
+ mPlaceholder = placeholder;
+ if (placeholder instanceof Spanned) {
+ mSpannedPlaceholder = (Spanned) placeholder;
+ } else {
+ mSpannedPlaceholder = null;
+ }
+ }
+
+ @Override
+ public int originalToTransformed(int offset, int strategy) {
+ if (offset < 0) return offset;
+ Preconditions.checkArgumentInRange(offset, 0, mOriginal.length(), "offset");
+ if (offset == mEnd && strategy == OffsetMapping.MAP_STRATEGY_CURSOR) {
+ // The offset equals to mEnd. For a cursor position it's considered before the
+ // inserted placeholder text.
+ return offset;
+ }
+ if (offset < mEnd) {
+ return offset;
+ }
+ return offset + mPlaceholder.length();
+ }
+
+ @Override
+ public int transformedToOriginal(int offset, int strategy) {
+ if (offset < 0) return offset;
+ Preconditions.checkArgumentInRange(offset, 0, length(), "offset");
+
+ // The placeholder text is inserted at mEnd. Because the offset is smaller than
+ // mEnd, we can directly return it.
+ if (offset < mEnd) return offset;
+ if (offset < mEnd + mPlaceholder.length()) {
+ return mEnd;
+ }
+ return offset - mPlaceholder.length();
+ }
+
+ @Override
+ public void originalToTransformed(TextUpdate textUpdate) {
+ if (textUpdate.where > mEnd) {
+ textUpdate.where += mPlaceholder.length();
+ } else if (textUpdate.where + textUpdate.before > mEnd) {
+ // The update also covers the placeholder string.
+ textUpdate.before += mPlaceholder.length();
+ textUpdate.after += mPlaceholder.length();
+ }
+ }
+
+ @Override
+ public int length() {
+ return mOriginal.length() + mPlaceholder.length();
+ }
+
+ @Override
+ public char charAt(int index) {
+ Preconditions.checkArgumentInRange(index, 0, length() - 1, "index");
+ if (index < mEnd) {
+ return mOriginal.charAt(index);
+ }
+ if (index < mEnd + mPlaceholder.length()) {
+ return mPlaceholder.charAt(index - mEnd);
+ }
+ return mOriginal.charAt(index - mPlaceholder.length());
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ if (end < start || start < 0 || end > length()) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (start == end) {
+ return "";
+ }
+
+ final int placeholderLength = mPlaceholder.length();
+
+ final int seg1Start = Math.min(start, mEnd);
+ final int seg1End = Math.min(end, mEnd);
+
+ final int seg2Start = MathUtils.constrain(start - mEnd, 0, placeholderLength);
+ final int seg2End = MathUtils.constrain(end - mEnd, 0, placeholderLength);
+
+ final int seg3Start = Math.max(start - placeholderLength, mEnd);
+ final int seg3End = Math.max(end - placeholderLength, mEnd);
+
+ return TextUtils.concat(
+ mOriginal.subSequence(seg1Start, seg1End),
+ mPlaceholder.subSequence(seg2Start, seg2End),
+ mOriginal.subSequence(seg3Start, seg3End));
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mOriginal.subSequence(0, mEnd))
+ + mPlaceholder
+ + mOriginal.subSequence(mEnd, mOriginal.length());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T[] getSpans(int start, int end, Class<T> type) {
+ if (end < start) {
+ return ArrayUtils.emptyArray(type);
+ }
+
+ final T[] spansOriginal;
+ if (mSpannedOriginal != null) {
+ final int originalStart =
+ transformedToOriginal(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int originalEnd =
+ transformedToOriginal(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+ spansOriginal = mSpannedOriginal.getSpans(originalStart, originalEnd, type);
+ } else {
+ spansOriginal = null;
+ }
+
+ final T[] spansPlaceholder;
+ if (mSpannedPlaceholder != null
+ && intersect(start, end, mEnd, mEnd + mPlaceholder.length())) {
+ final int placeholderStart = Math.max(start - mEnd, 0);
+ final int placeholderEnd = Math.min(end - mEnd, mPlaceholder.length());
+ spansPlaceholder =
+ mSpannedPlaceholder.getSpans(placeholderStart, placeholderEnd, type);
+ } else {
+ spansPlaceholder = null;
+ }
+
+ // TODO: sort the spans based on their priority.
+ return ArrayUtils.concat(type, spansOriginal, spansPlaceholder);
+ }
+
+ @Override
+ public int getSpanStart(Object tag) {
+ if (mSpannedOriginal != null) {
+ final int index = mSpannedOriginal.getSpanStart(tag);
+ if (index >= 0) {
+ if (index < mEnd) {
+ return index;
+ }
+ return index + mPlaceholder.length();
+ }
+ }
+
+ // The span is not on original text, try find it on the placeholder.
+ if (mSpannedPlaceholder != null) {
+ final int index = mSpannedPlaceholder.getSpanStart(tag);
+ if (index >= 0) {
+ // Find the span on placeholder, transform it and return.
+ return index + mEnd;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getSpanEnd(Object tag) {
+ if (mSpannedOriginal != null) {
+ final int index = mSpannedOriginal.getSpanEnd(tag);
+ if (index >= 0) {
+ if (index <= mEnd) {
+ return index;
+ }
+ return index + mPlaceholder.length();
+ }
+ }
+
+ // The span is not on original text, try find it on the placeholder.
+ if (mSpannedPlaceholder != null) {
+ final int index = mSpannedPlaceholder.getSpanEnd(tag);
+ if (index >= 0) {
+ // Find the span on placeholder, transform it and return.
+ return index + mEnd;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getSpanFlags(Object tag) {
+ if (mSpannedOriginal != null) {
+ final int flags = mSpannedOriginal.getSpanFlags(tag);
+ if (flags != 0) {
+ return flags;
+ }
+ }
+ if (mSpannedPlaceholder != null) {
+ return mSpannedPlaceholder.getSpanFlags(tag);
+ }
+ return 0;
+ }
+
+ @Override
+ public int nextSpanTransition(int start, int limit, Class type) {
+ if (limit <= start) return limit;
+ final Object[] spans = getSpans(start, limit, type);
+ for (int i = 0; i < spans.length; ++i) {
+ int spanStart = getSpanStart(spans[i]);
+ int spanEnd = getSpanEnd(spans[i]);
+ if (start < spanStart && spanStart < limit) {
+ limit = spanStart;
+ }
+ if (start < spanEnd && spanEnd < limit) {
+ limit = spanEnd;
+ }
+ }
+ return limit;
+ }
+
+ /**
+ * Return the start index of the highlight range for the insert mode, inclusive.
+ */
+ public int getHighlightStart() {
+ return mStart;
+ }
+
+ /**
+ * Return the end index of the highlight range for the insert mode, exclusive.
+ */
+ public int getHighlightEnd() {
+ return mEnd + mPlaceholder.length();
+ }
+ }
+
+ /**
+ * The placeholder span used for single line
+ */
+ public static class SingleLinePlaceholderSpan extends ReplacementSpan {
+ private final int mWidth;
+ SingleLinePlaceholderSpan(int width) {
+ mWidth = width;
+ }
+ @Override
+ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+ @Nullable Paint.FontMetricsInt fm) {
+ return mWidth;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
+ int top, int y, int bottom, @NonNull Paint paint) { }
+ }
+
+ /**
+ * Return true if the given two ranges intersects. This logic is the same one used in
+ * {@link Spanned} to determine whether a span range intersect with the query range.
+ */
+ private static boolean intersect(int s1, int e1, int s2, int e2) {
+ if (s1 > e2) return false;
+ if (e1 < s2) return false;
+ if (s1 != e1 && s2 != e2) {
+ if (s1 == e2) return false;
+ if (e1 == s2) return false;
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 2e4073e..8d221ab 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -19,6 +19,7 @@
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.graphics.Rect;
import android.view.inputmethod.InputMethodManager;
@@ -77,6 +78,15 @@
private int mConnectionCount = 0;
private final InputMethodManager mImm;
+ /**
+ * The handwrite-able View that is currently the target of a hovering stylus pointer. This is
+ * used to help determine whether the handwriting PointerIcon should be shown in
+ * {@link #onResolvePointerIcon(Context, MotionEvent)} so that we can reduce the number of calls
+ * to {@link #findBestCandidateView(float, float)}.
+ */
+ @Nullable
+ private View mCachedHoverTarget = null;
+
@VisibleForTesting
public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration,
@NonNull InputMethodManager inputMethodManager) {
@@ -308,6 +318,48 @@
}
/**
+ * Returns the pointer icon for the motion event, or null if it doesn't specify the icon.
+ * This gives HandwritingInitiator a chance to show the stylus handwriting icon over a
+ * handwrite-able area.
+ */
+ public PointerIcon onResolvePointerIcon(Context context, MotionEvent event) {
+ if (shouldShowHandwritingPointerIcon(event)) {
+ return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
+ }
+ return null;
+ }
+
+ private boolean shouldShowHandwritingPointerIcon(MotionEvent event) {
+ if (!event.isStylusPointer() || !event.isHoverEvent()) {
+ return false;
+ }
+
+ if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
+ || event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
+ final float hoverX = event.getX(event.getActionIndex());
+ final float hoverY = event.getY(event.getActionIndex());
+
+ if (mCachedHoverTarget != null) {
+ final Rect handwritingArea = getViewHandwritingArea(mCachedHoverTarget);
+ if (isInHandwritingArea(handwritingArea, hoverX, hoverY, mCachedHoverTarget)
+ && shouldTriggerStylusHandwritingForView(mCachedHoverTarget)) {
+ return true;
+ }
+ }
+
+ final View candidateView = findBestCandidateView(hoverX, hoverY);
+
+ if (candidateView != null) {
+ mCachedHoverTarget = candidateView;
+ return true;
+ }
+ }
+
+ mCachedHoverTarget = null;
+ return false;
+ }
+
+ /**
* Given the location of the stylus event, return the best candidate view to initialize
* handwriting mode.
*
@@ -437,7 +489,7 @@
* Return true if the (x, y) is inside by the given {@link Rect} with the View's
* handwriting bounds with offsets applied.
*/
- private boolean isInHandwritingArea(@Nullable Rect handwritingArea,
+ private static boolean isInHandwritingArea(@Nullable Rect handwritingArea,
float x, float y, View view) {
if (handwritingArea == null) return false;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index fc47e69..5810642 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -153,6 +153,12 @@
boolean performHapticFeedback(int effectId, boolean always);
/**
+ * Called by attached views to perform predefined haptic feedback without requiring VIBRATE
+ * permission.
+ */
+ oneway void performHapticFeedbackAsync(int effectId, boolean always);
+
+ /**
* Initiate the drag operation itself
*
* @param window Window which initiates drag operation.
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 8683cc2..47da3f6 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.hardware.BatteryState;
import android.hardware.SensorManager;
+import android.hardware.input.HostUsiVersion;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.lights.LightsManager;
@@ -83,7 +84,8 @@
private final boolean mHasButtonUnderPad;
private final boolean mHasSensor;
private final boolean mHasBattery;
- private final boolean mSupportsUsi;
+ private final HostUsiVersion mHostUsiVersion;
+ private final int mAssociatedDisplayId;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@GuardedBy("mMotionRanges")
@@ -467,7 +469,8 @@
int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag,
@Nullable String keyboardLayoutType, boolean hasVibrator, boolean hasMicrophone,
- boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, boolean supportsUsi) {
+ boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, int usiVersionMajor,
+ int usiVersionMinor, int associatedDisplayId) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -493,7 +496,8 @@
mHasSensor = hasSensor;
mHasBattery = hasBattery;
mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
- mSupportsUsi = supportsUsi;
+ mHostUsiVersion = new HostUsiVersion(usiVersionMajor, usiVersionMinor);
+ mAssociatedDisplayId = associatedDisplayId;
}
private InputDevice(Parcel in) {
@@ -515,7 +519,8 @@
mHasButtonUnderPad = in.readInt() != 0;
mHasSensor = in.readInt() != 0;
mHasBattery = in.readInt() != 0;
- mSupportsUsi = in.readInt() != 0;
+ mHostUsiVersion = HostUsiVersion.CREATOR.createFromParcel(in);
+ mAssociatedDisplayId = in.readInt();
mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
int numRanges = in.readInt();
@@ -554,7 +559,8 @@
private boolean mHasBattery = false;
private String mKeyboardLanguageTag = null;
private String mKeyboardLayoutType = null;
- private boolean mSupportsUsi = false;
+ private int mUsiVersionMajor = -1;
+ private int mUsiVersionMinor = -1;
private List<MotionRange> mMotionRanges = new ArrayList<>();
/** @see InputDevice#getId() */
@@ -665,9 +671,10 @@
return this;
}
- /** @see InputDevice#supportsUsi() () */
- public Builder setSupportsUsi(boolean supportsUsi) {
- mSupportsUsi = supportsUsi;
+ /** @see InputDevice#getHostUsiVersion() */
+ public Builder setUsiVersion(@Nullable HostUsiVersion usiVersion) {
+ mUsiVersionMajor = usiVersion != null ? usiVersion.getMajorVersion() : -1;
+ mUsiVersionMinor = usiVersion != null ? usiVersion.getMinorVersion() : -1;
return this;
}
@@ -699,7 +706,9 @@
mHasButtonUnderPad,
mHasSensor,
mHasBattery,
- mSupportsUsi);
+ mUsiVersionMajor,
+ mUsiVersionMinor,
+ Display.INVALID_DISPLAY);
final int numRanges = mMotionRanges.size();
for (int i = 0; i < numRanges; i++) {
@@ -1276,12 +1285,22 @@
}
/**
- * Reports whether the device supports the Universal Stylus Initiative (USI) protocol for
- * styluses.
+ * Reports the version of the Universal Stylus Initiative (USI) protocol supported by this
+ * input device.
+ *
+ * @return the supported USI version, or null if the device does not support USI
+ * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+ * @see InputManager#getHostUsiVersion(int)
* @hide
*/
- public boolean supportsUsi() {
- return mSupportsUsi;
+ @Nullable
+ public HostUsiVersion getHostUsiVersion() {
+ return mHostUsiVersion.isValid() ? mHostUsiVersion : null;
+ }
+
+ /** @hide */
+ public int getAssociatedDisplayId() {
+ return mAssociatedDisplayId;
}
/**
@@ -1415,7 +1434,8 @@
out.writeInt(mHasButtonUnderPad ? 1 : 0);
out.writeInt(mHasSensor ? 1 : 0);
out.writeInt(mHasBattery ? 1 : 0);
- out.writeInt(mSupportsUsi ? 1 : 0);
+ mHostUsiVersion.writeToParcel(out, flags);
+ out.writeInt(mAssociatedDisplayId);
int numRanges = mMotionRanges.size();
numRanges = numRanges > MAX_RANGES ? MAX_RANGES : numRanges;
@@ -1468,7 +1488,7 @@
description.append(" Has mic: ").append(mHasMicrophone).append("\n");
- description.append(" Supports USI: ").append(mSupportsUsi).append("\n");
+ description.append(" USI Version: ").append(getHostUsiVersion()).append("\n");
if (mKeyboardLanguageTag != null) {
description.append(" Keyboard language tag: ").append(mKeyboardLanguageTag).append(
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index df78827..99a7fe5 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -640,6 +640,10 @@
mConstructorArgs[0] = inflaterContext;
View result = root;
+ if (root != null && root.getViewRootImpl() != null) {
+ root.getViewRootImpl().notifyRendererOfExpensiveFrame();
+ }
+
try {
advanceToRootNode(parser);
final String name = parser.getName();
@@ -662,6 +666,10 @@
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
+ if (root == null && temp != null && temp.getViewRootImpl() != null) {
+ temp.getViewRootImpl().notifyRendererOfExpensiveFrame();
+ }
+
ViewGroup.LayoutParams params = null;
if (root != null) {
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 84bbdd1..a71ab8a 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2322,6 +2322,29 @@
}
/**
+ * Returns {@code true} if this motion event is from a stylus pointer.
+ * @hide
+ */
+ public boolean isStylusPointer() {
+ final int actionIndex = getActionIndex();
+ return isFromSource(InputDevice.SOURCE_STYLUS)
+ && (getToolType(actionIndex) == TOOL_TYPE_STYLUS
+ || getToolType(actionIndex) == TOOL_TYPE_ERASER);
+ }
+
+ /**
+ * Returns {@code true} if this motion event is a hover event, identified by it having an action
+ * of either {@link #ACTION_HOVER_ENTER}, {@link #ACTION_HOVER_MOVE} or
+ * {@link #ACTION_HOVER_EXIT}.
+ * @hide
+ */
+ public boolean isHoverEvent() {
+ return getActionMasked() == ACTION_HOVER_ENTER
+ || getActionMasked() == ACTION_HOVER_EXIT
+ || getActionMasked() == ACTION_HOVER_MOVE;
+ }
+
+ /**
* Gets the motion event flags.
*
* @see #FLAG_WINDOW_IS_OBSCURED
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 080c0d8..6a493e6 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -138,6 +138,9 @@
/** Type constant: grabbing. */
public static final int TYPE_GRABBING = 1021;
+ /** Type constant: handwriting. */
+ public static final int TYPE_HANDWRITING = 1022;
+
// OEM private types should be defined starting at this range to avoid
// conflicts with any system types that may be defined in the future.
private static final int TYPE_OEM_FIRST = 10000;
@@ -601,6 +604,8 @@
return com.android.internal.R.styleable.Pointer_pointerIconGrab;
case TYPE_GRABBING:
return com.android.internal.R.styleable.Pointer_pointerIconGrabbing;
+ case TYPE_HANDWRITING:
+ return com.android.internal.R.styleable.Pointer_pointerIconHandwriting;
default:
return 0;
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 3efbb75..f6348d7 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -485,7 +485,6 @@
* SurfaceControlViewHost was created with an associated hostInputToken.
*
* @return Whether the touch stream was transferred.
- * @hide
*/
public boolean transferTouchGestureToHost() {
if (mViewRoot == null) {
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 164a494..9c6e823 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -594,6 +594,13 @@
}
}
+ @Override
+ public void notifyExpensiveFrame() {
+ if (isEnabled()) {
+ super.notifyExpensiveFrame();
+ }
+ }
+
/**
* Updates the light position based on the position of the window.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 175c76b..5165478 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -280,6 +280,14 @@
private static final boolean ENABLE_INPUT_LATENCY_TRACKING = true;
/**
+ * Controls whether to use the new oneway performHapticFeedback call. This returns
+ * true in a few more conditions, but doesn't affect which haptics happen. Notably, it
+ * makes the call to performHapticFeedback non-blocking, which reduces potential UI jank.
+ * This is intended as a temporary flag, ultimately becoming permanently 'true'.
+ */
+ private static final boolean USE_ASYNC_PERFORM_HAPTIC_FEEDBACK = true;
+
+ /**
* Whether the caption is drawn by the shell.
* @hide
*/
@@ -2326,6 +2334,18 @@
}
}
+ /**
+ * Notifies the HardwareRenderer of an expensive upcoming frame, to
+ * allow better handling of power and scheduling requirements.
+ *
+ * @hide
+ */
+ void notifyRendererOfExpensiveFrame() {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.notifyExpensiveFrame();
+ }
+ }
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
@@ -6836,7 +6856,13 @@
}
private void maybeUpdatePointerIcon(MotionEvent event) {
- if (event.getPointerCount() == 1 && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (event.getPointerCount() != 1) {
+ return;
+ }
+ final boolean needsStylusPointerIcon =
+ InputManager.getInstance().isStylusPointerIconEnabled()
+ && event.isStylusPointer();
+ if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
|| event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
// Other apps or the window manager may change the icon type outside of
@@ -6904,7 +6930,17 @@
Slog.d(mTag, "updatePointerIcon called with position out of bounds");
return false;
}
- final PointerIcon pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
+
+ PointerIcon pointerIcon = null;
+
+ if (event.isStylusPointer() && InputManager.getInstance().isStylusPointerIconEnabled()) {
+ pointerIcon = mHandwritingInitiator.onResolvePointerIcon(mContext, event);
+ }
+
+ if (pointerIcon == null) {
+ pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
+ }
+
final int pointerType = (pointerIcon != null) ?
pointerIcon.getType() : PointerIcon.TYPE_DEFAULT;
@@ -8567,7 +8603,13 @@
}
try {
- return mWindowSession.performHapticFeedback(effectId, always);
+ if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) {
+ mWindowSession.performHapticFeedbackAsync(effectId, always);
+ return true;
+ } else {
+ // Original blocking binder call path.
+ return mWindowSession.performHapticFeedback(effectId, always);
+ }
} catch (RemoteException e) {
return false;
}
diff --git a/core/java/android/view/WindowAnimationFrameStats.java b/core/java/android/view/WindowAnimationFrameStats.java
index 251ae9b..25f29a7 100644
--- a/core/java/android/view/WindowAnimationFrameStats.java
+++ b/core/java/android/view/WindowAnimationFrameStats.java
@@ -32,7 +32,12 @@
* #getRefreshPeriodNano()}. If the system does not render a frame every refresh
* period the user will see irregular window transitions. The time when the frame was
* actually presented on the display by calling {@link #getFramePresentedTimeNano(int)}.
+ *
+ * @deprecated Use Shared
+ * <a href="https://perfetto.dev/docs/data-sources/frametimeline">FrameTimeline</a>
+ * jank metrics instead.
*/
+@Deprecated
public final class WindowAnimationFrameStats extends FrameStats implements Parcelable {
/**
* @hide
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 4ddd485..7d37c50 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -466,6 +466,11 @@
}
@Override
+ public void performHapticFeedbackAsync(int effectId, boolean always) {
+ performHapticFeedback(effectId, always);
+ }
+
+ @Override
public android.os.IBinder performDrag(android.view.IWindow window, int flags,
android.view.SurfaceControl surface, int touchSource, float touchX, float touchY,
float thumbCenterX, float thumbCenterY, android.content.ClipData data) {
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 9ebaa67..4e5cec7 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -568,6 +568,8 @@
supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT_RANGE;
} else if (gesture.equals(InsertGesture.class)) {
supportedTypes |= HandwritingGesture.GESTURE_TYPE_INSERT;
+ } else if (gesture.equals(InsertModeGesture.class)) {
+ supportedTypes |= HandwritingGesture.GESTURE_TYPE_INSERT_MODE;
} else if (gesture.equals(DeleteGesture.class)) {
supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE;
} else if (gesture.equals(DeleteRangeGesture.class)) {
@@ -611,6 +613,10 @@
== HandwritingGesture.GESTURE_TYPE_INSERT) {
list.add(InsertGesture.class);
}
+ if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_INSERT_MODE)
+ == HandwritingGesture.GESTURE_TYPE_INSERT_MODE) {
+ list.add(InsertModeGesture.class);
+ }
if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_DELETE)
== HandwritingGesture.GESTURE_TYPE_DELETE) {
list.add(DeleteGesture.class);
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index 1f4a7af..e7207fa 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -142,6 +142,13 @@
public static final int GESTURE_TYPE_DELETE_RANGE = 1 << 6;
/**
+ * Gesture of type {@link InsertModeGesture} to begin an insert mode at a designated point.
+ * @hide
+ */
+ @TestApi
+ public static final int GESTURE_TYPE_INSERT_MODE = 1 << 7;
+
+ /**
* Type of gesture like {@link #GESTURE_TYPE_SELECT}, {@link #GESTURE_TYPE_INSERT},
* or {@link #GESTURE_TYPE_DELETE}.
*/
@@ -150,6 +157,7 @@
GESTURE_TYPE_SELECT,
GESTURE_TYPE_SELECT_RANGE,
GESTURE_TYPE_INSERT,
+ GESTURE_TYPE_INSERT_MODE,
GESTURE_TYPE_DELETE,
GESTURE_TYPE_DELETE_RANGE,
GESTURE_TYPE_REMOVE_SPACE,
@@ -168,6 +176,7 @@
GESTURE_TYPE_SELECT,
GESTURE_TYPE_SELECT_RANGE,
GESTURE_TYPE_INSERT,
+ GESTURE_TYPE_INSERT_MODE,
GESTURE_TYPE_DELETE,
GESTURE_TYPE_DELETE_RANGE,
GESTURE_TYPE_REMOVE_SPACE,
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 229cc02..ec1badb 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -22,6 +22,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -38,6 +39,7 @@
import android.inputmethodservice.InputMethodService;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Printer;
import android.util.Slog;
@@ -71,6 +73,16 @@
* @attr ref android.R.styleable#InputMethod_configChanges
*/
public final class InputMethodInfo implements Parcelable {
+
+ /**
+ * {@link Intent#getAction() Intent action} for IME that
+ * {@link #supportsStylusHandwriting() supports stylus handwriting}.
+ *
+ * @see #createStylusHandwritingSettingsActivityIntent().
+ */
+ public static final String ACTION_STYLUS_HANDWRITING_SETTINGS =
+ "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS";
+
static final String TAG = "InputMethodInfo";
/**
@@ -152,6 +164,11 @@
*/
private final boolean mSupportsStylusHandwriting;
+ /**
+ * The stylus handwriting setting activity's name, used by the system settings to
+ * launch the stylus handwriting specific setting activity of this input method.
+ */
+ private final String mStylusHandwritingSettingsActivityAttr;
/**
* @param service the {@link ResolveInfo} corresponds in which the IME is implemented.
@@ -203,6 +220,7 @@
PackageManager pm = context.getPackageManager();
String settingsActivityComponent = null;
+ String stylusHandwritingSettingsActivity = null;
boolean isVrOnly;
int isDefaultResId = 0;
@@ -253,6 +271,8 @@
com.android.internal.R.styleable.InputMethod_configChanges, 0);
mSupportsStylusHandwriting = sa.getBoolean(
com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false);
+ stylusHandwritingSettingsActivity = sa.getString(
+ com.android.internal.R.styleable.InputMethod_stylusHandwritingSettingsActivity);
sa.recycle();
final int depth = parser.getDepth();
@@ -328,6 +348,7 @@
}
mSubtypes = new InputMethodSubtypeArray(subtypes);
mSettingsActivityName = settingsActivityComponent;
+ mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivity;
mIsDefaultResId = isDefaultResId;
mIsAuxIme = isAuxIme;
mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
@@ -359,6 +380,7 @@
mHandledConfigChanges = source.mHandledConfigChanges;
mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
mForceDefault = source.mForceDefault;
+ mStylusHandwritingSettingsActivityAttr = source.mStylusHandwritingSettingsActivityAttr;
}
InputMethodInfo(Parcel source) {
@@ -376,6 +398,7 @@
mSubtypes = new InputMethodSubtypeArray(source);
mHandledConfigChanges = source.readInt();
mSupportsStylusHandwriting = source.readBoolean();
+ mStylusHandwritingSettingsActivityAttr = source.readString8();
mForceDefault = false;
}
@@ -389,10 +412,28 @@
false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
0 /* handledConfigChanges */, false /* supportsStylusHandwriting */,
+ null /* stylusHandwritingSettingsActivityAttr */,
false /* inlineSuggestionsEnabled */);
}
/**
+ * Test API for creating a built-in input method to verify stylus handwriting.
+ * @hide
+ */
+ @TestApi
+ public InputMethodInfo(@NonNull String packageName, @NonNull String className,
+ @NonNull CharSequence label, @NonNull String settingsActivity,
+ boolean supportStylusHandwriting,
+ @NonNull String stylusHandwritingSettingsActivityAttr) {
+ this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+ settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
+ false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+ false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
+ 0 /* handledConfigChanges */, supportStylusHandwriting,
+ stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
+ }
+
+ /**
* Temporary API for creating a built-in input method for test.
* @hide
*/
@@ -405,6 +446,7 @@
false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges,
false /* supportsStylusHandwriting */,
+ null /* stylusHandwritingSettingsActivityAttr */,
false /* inlineSuggestionsEnabled */);
}
@@ -419,6 +461,7 @@
true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
false /* isVrOnly */, 0 /* handledconfigChanges */,
false /* supportsStylusHandwriting */,
+ null /* stylusHandwritingSettingsActivityAttr */,
false /* inlineSuggestionsEnabled */);
}
@@ -432,6 +475,7 @@
this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
0 /* handledConfigChanges */, false /* supportsStylusHandwriting */,
+ null /* stylusHandwritingSettingsActivityAttr */,
false /* inlineSuggestionsEnabled */);
}
@@ -443,6 +487,7 @@
List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
boolean isVrOnly, int handledConfigChanges, boolean supportsStylusHandwriting,
+ String stylusHandwritingSettingsActivityAttr,
boolean supportsInlineSuggestionsWithTouchExploration) {
final ServiceInfo si = ri.serviceInfo;
mService = ri;
@@ -461,6 +506,7 @@
mIsVrOnly = isVrOnly;
mHandledConfigChanges = handledConfigChanges;
mSupportsStylusHandwriting = supportsStylusHandwriting;
+ mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivityAttr;
}
private static ResolveInfo buildFakeResolveInfo(String packageName, String className,
@@ -550,6 +596,7 @@
*
* <p>A null will be returned if there is no settings activity associated
* with the input method.</p>
+ * @see #createStylusHandwritingSettingsActivityIntent()
*/
public String getSettingsActivity() {
return mSettingsActivityName;
@@ -622,11 +669,41 @@
/**
* Returns if IME supports handwriting using stylus input.
* @attr ref android.R.styleable#InputMethod_supportsStylusHandwriting
+ * @see #createStylusHandwritingSettingsActivityIntent()
*/
public boolean supportsStylusHandwriting() {
return mSupportsStylusHandwriting;
}
+ /**
+ * Returns {@link Intent} for stylus handwriting settings activity with
+ * {@link Intent#getAction() Intent action} {@link #ACTION_STYLUS_HANDWRITING_SETTINGS}
+ * if IME {@link #supportsStylusHandwriting() supports stylus handwriting}, else
+ * <code>null</code> if there are no associated settings for stylus handwriting / handwriting
+ * is not supported or if
+ * {@link android.R.styleable#InputMethod_stylusHandwritingSettingsActivity} is not defined.
+ *
+ * <p>To launch stylus settings, use this method to get the {@link android.content.Intent} to
+ * launch the stylus handwriting settings activity.</p>
+ * <p>e.g.<pre><code>startActivity(createStylusHandwritingSettingsActivityIntent());</code>
+ * </pre></p>
+ *
+ * @attr ref R.styleable#InputMethod_stylusHandwritingSettingsActivity
+ * @see #getSettingsActivity()
+ * @see #supportsStylusHandwriting()
+ */
+ @Nullable
+ public Intent createStylusHandwritingSettingsActivityIntent() {
+ if (TextUtils.isEmpty(mStylusHandwritingSettingsActivityAttr)
+ || !mSupportsStylusHandwriting) {
+ return null;
+ }
+ // TODO(b/210039666): consider returning null if component is not enabled.
+ return new Intent(ACTION_STYLUS_HANDWRITING_SETTINGS).setComponent(
+ new ComponentName(getServiceInfo().packageName,
+ mStylusHandwritingSettingsActivityAttr));
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mId=" + mId
+ " mSettingsActivityName=" + mSettingsActivityName
@@ -637,7 +714,9 @@
+ mSupportsInlineSuggestionsWithTouchExploration
+ " mSuppressesSpellChecker=" + mSuppressesSpellChecker
+ " mShowInInputMethodPicker=" + mShowInInputMethodPicker
- + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting);
+ + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting
+ + " mStylusHandwritingSettingsActivityAttr="
+ + mStylusHandwritingSettingsActivityAttr);
pw.println(prefix + "mIsDefaultResId=0x"
+ Integer.toHexString(mIsDefaultResId));
pw.println(prefix + "Service:");
@@ -752,6 +831,7 @@
mSubtypes.writeToParcel(dest);
dest.writeInt(mHandledConfigChanges);
dest.writeBoolean(mSupportsStylusHandwriting);
+ dest.writeString8(mStylusHandwritingSettingsActivityAttr);
}
/**
diff --git a/core/java/android/view/inputmethod/InsertModeGesture.aidl b/core/java/android/view/inputmethod/InsertModeGesture.aidl
new file mode 100644
index 0000000..a0790b8
--- /dev/null
+++ b/core/java/android/view/inputmethod/InsertModeGesture.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.inputmethod;
+
+parcelable InsertModeGesture;
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/InsertModeGesture.java b/core/java/android/view/inputmethod/InsertModeGesture.java
new file mode 100644
index 0000000..6b9d7fb
--- /dev/null
+++ b/core/java/android/view/inputmethod/InsertModeGesture.java
@@ -0,0 +1,187 @@
+/*
+ * 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 android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.graphics.PointF;
+import android.os.CancellationSignal;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * A sub-class of {@link HandwritingGesture} for starting an insert mode which inserts a space in
+ * the editor to let users hand write freely at the designated insertion point.
+ * This class holds the information required for insertion of text in
+ * toolkit widgets like {@link TextView}.
+ *
+ * Once InsertMode gesture is started, it continues until IME calls
+ * {@link CancellationSignal#cancel()} and toolkit can receive cancel using
+ * {@link CancellationSignal#setOnCancelListener(CancellationSignal.OnCancelListener)} obtained from
+ * {@link #getCancellationSignal()}.
+ */
+public final class InsertModeGesture extends HandwritingGesture implements Parcelable {
+
+ private PointF mPoint;
+ private CancellationSignal mCancellationSignal;
+
+ private InsertModeGesture(PointF point, String fallbackText,
+ CancellationSignal cancellationSignal) {
+ mType = GESTURE_TYPE_INSERT_MODE;
+ mPoint = point;
+ mFallbackText = fallbackText;
+ mCancellationSignal = cancellationSignal;
+ }
+
+ private InsertModeGesture(final Parcel source) {
+ mType = GESTURE_TYPE_INSERT_MODE;
+ mFallbackText = source.readString8();
+ mPoint = source.readTypedObject(PointF.CREATOR);
+ }
+
+ /**
+ * Returns the {@link CancellationSignal} associated with finishing this gesture.
+ * Once InsertMode gesture is started, it continues until IME calls
+ * {@link CancellationSignal#cancel()} and toolkit can receive cancel using
+ * {@link CancellationSignal#setOnCancelListener(CancellationSignal.OnCancelListener)}.
+ */
+ @NonNull
+ public CancellationSignal getCancellationSignal() {
+ return mCancellationSignal;
+ }
+
+ /**
+ * Returns the insertion point {@link PointF} (in screen coordinates) where space will be
+ * created for additional text to be inserted.
+ */
+ @NonNull
+ public PointF getInsertionPoint() {
+ return mPoint;
+ }
+
+ /**
+ * Builder for {@link InsertModeGesture}. This class is not designed to be thread-safe.
+ */
+ public static final class Builder {
+ private PointF mPoint;
+ private String mFallbackText;
+ // TODO(b/254727073): implement CancellationSignal
+ private CancellationSignal mCancellationSignal;
+
+ /**
+ * Sets the insertion point (in screen coordinates) where space will be created for
+ * additional text to be inserted.
+ */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setInsertionPoint(@NonNull PointF point) {
+ mPoint = point;
+ return this;
+ }
+
+ /**
+ * Sets the {@link CancellationSignal} used to cancel the ongoing gesture.
+ * @param cancellationSignal signal to cancel an ongoing gesture.
+ */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setCancellationSignal(@NonNull CancellationSignal cancellationSignal) {
+ mCancellationSignal = cancellationSignal;
+ return this;
+ }
+
+ /**
+ * Set fallback text that will be committed at current cursor position if there is no
+ * applicable text beneath the area of gesture.
+ * @param fallbackText text to set
+ */
+ @NonNull
+ public Builder setFallbackText(@Nullable String fallbackText) {
+ mFallbackText = fallbackText;
+ return this;
+ }
+
+ /**
+ * Returns {@link InsertModeGesture} using parameters in this
+ * {@link InsertModeGesture.Builder}.
+ * @throws IllegalArgumentException if one or more positional parameters are not specified.
+ */
+ @NonNull
+ public InsertModeGesture build() {
+ if (mPoint == null) {
+ throw new IllegalArgumentException("Insertion point must be set.");
+ } else if (mCancellationSignal == null) {
+ throw new IllegalArgumentException("CancellationSignal must be set.");
+ }
+ return new InsertModeGesture(mPoint, mFallbackText, mCancellationSignal);
+ }
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ @NonNull
+ public static final Creator<InsertModeGesture> CREATOR = new Creator<>() {
+ @Override
+ public InsertModeGesture createFromParcel(Parcel source) {
+ return new InsertModeGesture(source);
+ }
+
+ @Override
+ public InsertModeGesture[] newArray(int size) {
+ return new InsertModeGesture[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPoint, mFallbackText);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof InsertModeGesture)) return false;
+
+ InsertModeGesture that = (InsertModeGesture) o;
+
+ if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
+ return Objects.equals(mPoint, that.mPoint);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mFallbackText);
+ dest.writeTypedObject(mPoint, flags);
+ }
+}
diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
index e4066fc..ae7df0f 100644
--- a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
+++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
@@ -70,6 +70,8 @@
return SelectRangeGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_INSERT:
return InsertGesture.CREATOR.createFromParcel(parcel);
+ case HandwritingGesture.GESTURE_TYPE_INSERT_MODE:
+ return InsertModeGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_DELETE:
return DeleteGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_DELETE_RANGE:
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 95a42aa..f2f4557 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -73,10 +73,12 @@
import android.text.SpannedString;
import android.text.StaticLayout;
import android.text.TextUtils;
+import android.text.method.InsertModeTransformationMethod;
import android.text.method.KeyListener;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
import android.text.method.OffsetMapping;
+import android.text.method.TransformationMethod;
import android.text.method.WordIterator;
import android.text.style.EasyEditSpan;
import android.text.style.SuggestionRangeSpan;
@@ -136,6 +138,7 @@
import android.window.OnBackInvokedDispatcher;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.inputmethod.EditableInputConnection;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -461,6 +464,7 @@
private int mLineChangeSlopMin;
private final AccessibilitySmartActions mA11ySmartActions;
+ private InsertModeController mInsertModeController;
Editor(TextView textView) {
mTextView = textView;
@@ -2109,6 +2113,10 @@
}
}
+ if (mInsertModeController != null) {
+ mInsertModeController.onDraw(canvas);
+ }
+
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
@@ -8054,6 +8062,110 @@
}
}
+ private static final class InsertModeController {
+ private final TextView mTextView;
+ private boolean mIsInsertModeActive;
+ private InsertModeTransformationMethod mInsertModeTransformationMethod;
+ private final Paint mHighlightPaint;
+
+ InsertModeController(@NonNull TextView textView) {
+ mTextView = Objects.requireNonNull(textView);
+ mIsInsertModeActive = false;
+ mInsertModeTransformationMethod = null;
+ mHighlightPaint = new Paint();
+
+ // The highlight color is supposed to be 12% of the color primary40. We can't
+ // directly access Material 3 theme. But because Material 3 sets the colorPrimary to
+ // be primary40, here we hardcoded it to be 12% of colorPrimary.
+ final TypedValue typedValue = new TypedValue();
+ mTextView.getContext().getTheme()
+ .resolveAttribute(R.attr.colorPrimary, typedValue, true);
+ final int colorPrimary = typedValue.data;
+ final int highlightColor = ColorUtils.setAlphaComponent(colorPrimary,
+ (int) (0.12f * Color.alpha(colorPrimary)));
+ mHighlightPaint.setColor(highlightColor);
+ }
+
+ /**
+ * Enter insert mode.
+ * @param offset the index to set the cursor.
+ * @return true if the call is successful. false if a) it's already in the insert mode,
+ * b) it failed to enter the insert mode.
+ */
+ boolean enterInsertMode(int offset) {
+ if (mIsInsertModeActive) return false;
+
+ TransformationMethod oldTransformationMethod =
+ mTextView.getTransformationMethod();
+ if (oldTransformationMethod instanceof OffsetMapping) {
+ // We can't support the case where the oldTransformationMethod is an OffsetMapping.
+ return false;
+ }
+
+ final boolean isSingleLine = mTextView.isSingleLine();
+ mInsertModeTransformationMethod = new InsertModeTransformationMethod(offset,
+ isSingleLine, oldTransformationMethod);
+ mTextView.setTransformationMethod(mInsertModeTransformationMethod);
+ Selection.setSelection((Spannable) mTextView.getText(), offset);
+
+ mIsInsertModeActive = true;
+ return true;
+ }
+
+ void exitInsertMode() {
+ if (!mIsInsertModeActive) return;
+ if (mInsertModeTransformationMethod == null
+ || mInsertModeTransformationMethod != mTextView.getTransformationMethod()) {
+ // If mInsertionModeTransformationMethod doesn't match the one on TextView,
+ // something else have changed the TextView's TransformationMethod while the
+ // insertion mode is active. We don't need to restore the oldTransformationMethod.
+ // TODO(265871733): support the case where setTransformationMethod is called in
+ // the insert mode.
+ mIsInsertModeActive = false;
+ return;
+ }
+ // Changing TransformationMethod will reset selection range to [0, 0), we need to
+ // manually restore the old selection range.
+ final int selectionStart = mTextView.getSelectionStart();
+ final int selectionEnd = mTextView.getSelectionEnd();
+ final TransformationMethod oldTransformationMethod =
+ mInsertModeTransformationMethod.getOldTransformationMethod();
+ mTextView.setTransformationMethod(oldTransformationMethod);
+ Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
+ mIsInsertModeActive = false;
+ }
+
+ void onDraw(Canvas canvas) {
+ if (!mIsInsertModeActive) return;
+ final CharSequence transformedText = mTextView.getTransformed();
+ if (transformedText instanceof InsertModeTransformationMethod.TransformedText) {
+ final Layout layout = mTextView.getLayout();
+ if (layout == null) return;
+ final InsertModeTransformationMethod.TransformedText insertModeTransformedText =
+ ((InsertModeTransformationMethod.TransformedText) transformedText);
+ final int highlightStart = insertModeTransformedText.getHighlightStart();
+ final int highlightEnd = insertModeTransformedText.getHighlightEnd();
+ final Layout.SelectionRectangleConsumer consumer =
+ (left, top, right, bottom, textSelectionLayout) ->
+ canvas.drawRect(left, top, right, bottom, mHighlightPaint);
+ layout.getSelection(highlightStart, highlightEnd, consumer);
+ }
+ }
+ }
+
+ boolean enterInsertMode(int offset) {
+ if (mInsertModeController == null) {
+ if (mTextView == null) return false;
+ mInsertModeController = new InsertModeController(mTextView);
+ }
+ return mInsertModeController.enterInsertMode(offset);
+ }
+
+ void exitInsertMode() {
+ if (mInsertModeController == null) return;
+ mInsertModeController.exitInsertMode();
+ }
+
/**
* Initializes the nodeInfo with smart actions.
*/
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6ff808e..6e36a03 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -202,6 +202,7 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.InsertModeGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
@@ -2534,7 +2535,7 @@
/**
* @hide
*/
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public CharSequence getTransformed() {
return mTransformed;
}
@@ -9620,6 +9621,7 @@
gestures.add(InsertGesture.class);
gestures.add(RemoveSpaceGesture.class);
gestures.add(JoinOrSplitGesture.class);
+ gestures.add(InsertModeGesture.class);
outAttrs.setSupportedHandwritingGestures(gestures);
Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
@@ -10169,6 +10171,27 @@
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
+ /** @hide */
+ public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) {
+ final PointF insertPoint =
+ convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
+ final int line = getLineForHandwritingGesture(insertPoint);
+ final CancellationSignal cancellationSignal = gesture.getCancellationSignal();
+
+ // If no cancellationSignal is provided, don't enter the insert mode.
+ if (line == -1 || cancellationSignal == null) {
+ return handleGestureFailure(gesture);
+ }
+
+ final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x);
+
+ if (!mEditor.enterInsertMode(offset)) {
+ return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
+ }
+ cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode());
+ return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+ }
+
private int handleGestureFailure(HandwritingGesture gesture) {
return handleGestureFailure(gesture, /* isPreview= */ false);
}
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index 9759540..930f6e0 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -30,11 +30,16 @@
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Used to sort resolved activities in {@link ResolverListController}.
@@ -48,8 +53,8 @@
private static final String TAG = "AbstractResolverComp";
protected AfterCompute mAfterCompute;
- protected final PackageManager mPm;
- protected final UsageStatsManager mUsm;
+ protected final Map<UserHandle, PackageManager> mPmMap = new HashMap<>();
+ protected final Map<UserHandle, UsageStatsManager> mUsmMap = new HashMap<>();
protected String[] mAnnotations;
protected String mContentType;
@@ -98,14 +103,28 @@
}
};
- public AbstractResolverComparator(Context context, Intent intent) {
+ // context here refers to the activity calling this comparator.
+ // targetUserSpace refers to the userSpace in which the targets to be ranked lie.
+ public AbstractResolverComparator(Context launchedFromContext, Intent intent,
+ UserHandle targetUserSpace) {
+ this(launchedFromContext, intent, Lists.newArrayList(targetUserSpace));
+ }
+
+ // context here refers to the activity calling this comparator.
+ // targetUserSpaceList refers to the userSpace(s) in which the targets to be ranked lie.
+ public AbstractResolverComparator(Context launchedFromContext, Intent intent,
+ List<UserHandle> targetUserSpaceList) {
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mContentType = intent.getType();
getContentAnnotations(intent);
- mPm = context.getPackageManager();
- mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
- mAzComparator = new AzInfoComparator(context);
+ for (UserHandle user : targetUserSpaceList) {
+ Context userContext = launchedFromContext.createContextAsUser(user, 0);
+ mPmMap.put(user, userContext.getPackageManager());
+ mUsmMap.put(user,
+ (UsageStatsManager) userContext.getSystemService(Context.USAGE_STATS_SERVICE));
+ }
+ mAzComparator = new AzInfoComparator(launchedFromContext);
}
// get annotations of content from intent.
@@ -208,8 +227,8 @@
/**
* Computes features for each target. This will be called before calls to {@link
- * #getScore(ComponentName)} or {@link #compare(Object, Object)}, in order to prepare the
- * comparator for those calls. Note that {@link #getScore(ComponentName)} uses {@link
+ * #getScore(TargetInfo)} or {@link #compare(ResolveInfo, ResolveInfo)}, in order to prepare the
+ * comparator for those calls. Note that {@link #getScore(TargetInfo)} uses {@link
* ComponentName}, so the implementation will have to be prepared to identify a {@link
* ResolvedComponentInfo} by {@link ComponentName}. {@link #beforeCompute()} will be called
* before doing any computing.
@@ -226,7 +245,7 @@
* Returns the score that was calculated for the corresponding {@link ResolvedComponentInfo}
* when {@link #compute(List)} was called before this.
*/
- abstract float getScore(ComponentName name);
+ abstract float getScore(TargetInfo targetInfo);
/** Handles result message sent to mHandler. */
abstract void handleResultMessage(Message message);
@@ -234,9 +253,11 @@
/**
* Reports to UsageStats what was chosen.
*/
- final void updateChooserCounts(String packageName, int userId, String action) {
- if (mUsm != null) {
- mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
+ final void updateChooserCounts(String packageName, UserHandle user, String action) {
+ if (mUsmMap.containsKey(user)) {
+ mUsmMap.get(user)
+ .reportChooserSelection(packageName, user.getIdentifier(), mContentType,
+ mAnnotations, action);
}
}
@@ -246,9 +267,9 @@
* <p>Default implementation does nothing, as we could have simple model that does not train
* online.
*
- * @param componentName the component that the user clicked
+ * @param targetInfo the target that the user clicked.
*/
- void updateModel(ComponentName componentName) {
+ void updateModel(TargetInfo targetInfo) {
}
/** Called before {@link #doCompute(List)}. Sets up 500ms timeout. */
diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index 115a9d7..b9f0236 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -31,6 +31,9 @@
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
@@ -70,7 +73,7 @@
AppPredictor appPredictor,
UserHandle user,
ChooserActivityLogger chooserActivityLogger) {
- super(context, intent);
+ super(context, intent, Lists.newArrayList(user));
mContext = context;
mIntent = intent;
mAppPredictor = appPredictor;
@@ -99,13 +102,13 @@
}
@Override
- float getScore(ComponentName name) {
- return mComparatorModel.getScore(name);
+ float getScore(TargetInfo targetInfo) {
+ return mComparatorModel.getScore(targetInfo);
}
@Override
- void updateModel(ComponentName componentName) {
- mComparatorModel.notifyOnTargetSelected(componentName);
+ void updateModel(TargetInfo targetInfo) {
+ mComparatorModel.notifyOnTargetSelected(targetInfo);
}
@Override
@@ -158,9 +161,12 @@
private void setupFallbackModel(List<ResolvedComponentInfo> targets) {
mResolverRankerService =
new ResolverRankerServiceResolverComparator(
- mContext, mIntent, mReferrerPackage,
+ mContext,
+ mIntent,
+ mReferrerPackage,
() -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT),
- getChooserActivityLogger());
+ getChooserActivityLogger(),
+ mUser);
mComparatorModel = mModelBuilder.buildFallbackModel(mResolverRankerService);
mResolverRankerService.compute(targets);
}
@@ -224,13 +230,13 @@
}
@Override
- public float getScore(ComponentName componentName) {
- return comparator.getScore(componentName);
+ public float getScore(TargetInfo targetInfo) {
+ return comparator.getScore(targetInfo);
}
@Override
- public void notifyOnTargetSelected(ComponentName componentName) {
- comparator.updateModel(componentName);
+ public void notifyOnTargetSelected(TargetInfo targetInfo) {
+ comparator.updateModel(targetInfo);
}
};
}
@@ -271,8 +277,8 @@
}
@Override
- public float getScore(ComponentName name) {
- Integer rank = mTargetRanks.get(name);
+ public float getScore(TargetInfo targetInfo) {
+ Integer rank = mTargetRanks.get(targetInfo.getResolvedComponentName());
if (rank == null) {
Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
return 0f;
@@ -282,13 +288,14 @@
}
@Override
- public void notifyOnTargetSelected(ComponentName componentName) {
+ public void notifyOnTargetSelected(TargetInfo targetInfo) {
mAppPredictor.notifyAppTargetEvent(
new AppTargetEvent.Builder(
new AppTarget.Builder(
- new AppTargetId(componentName.toString()),
- componentName.getPackageName(), mUser)
- .setClassName(componentName.getClassName()).build(),
+ new AppTargetId(targetInfo.getResolvedComponentName().toString()),
+ targetInfo.getResolvedComponentName().getPackageName(), mUser)
+ .setClassName(targetInfo.getResolvedComponentName()
+ .getClassName()).build(),
ACTION_LAUNCH).build());
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 791819b..f257f1c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2268,9 +2268,11 @@
mChooserMultiProfilePagerAdapter.getActiveListAdapter();
if (currentListAdapter != null) {
sendImpressionToAppPredictor(info, currentListAdapter);
- currentListAdapter.updateModel(info.getResolvedComponentName());
- currentListAdapter.updateChooserCounts(ri.activityInfo.packageName,
- targetIntent.getAction());
+ currentListAdapter.updateModel(info);
+ currentListAdapter.updateChooserCounts(
+ ri.activityInfo.packageName,
+ targetIntent.getAction(),
+ ri.userHandle);
}
if (DEBUG) {
Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
@@ -2395,7 +2397,10 @@
*/
@Nullable
private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
- return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
+ // We cannot use APS service when clone profile is present as APS service cannot sort
+ // cross profile targets as of now.
+ return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES && getCloneProfileUserHandle() == null
+ ? createAppPredictor(userHandle) : null;
}
void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
@@ -2548,8 +2553,13 @@
getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger());
} else {
resolverComparator =
- new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
- getReferrerPackageName(), null, getChooserActivityLogger());
+ new ResolverRankerServiceResolverComparator(
+ this,
+ getTargetIntent(),
+ getReferrerPackageName(),
+ null,
+ getChooserActivityLogger(),
+ getResolverRankerServiceUserHandleList(userHandle));
}
UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 3efd279..3619c7b 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -21,6 +21,7 @@
import android.app.ListFragment;
import android.content.Context;
import android.os.Bundle;
+import android.os.LocaleList;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
@@ -102,15 +103,21 @@
public static LocalePickerWithRegion createLanguagePicker(Context context,
LocaleSelectedListener listener, boolean translatedOnly) {
- return createLanguagePicker(context, listener, translatedOnly, null, null);
+ return createLanguagePicker(context, listener, translatedOnly, null, null, null);
}
public static LocalePickerWithRegion createLanguagePicker(Context context,
- LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
- OnActionExpandListener onActionExpandListener) {
+ LocaleSelectedListener listener, boolean translatedOnly, LocaleList explicitLocales) {
+ return createLanguagePicker(context, listener, translatedOnly, explicitLocales, null, null);
+ }
+
+ /** Creates language picker UI */
+ public static LocalePickerWithRegion createLanguagePicker(Context context,
+ LocaleSelectedListener listener, boolean translatedOnly, LocaleList explicitLocales,
+ String appPackageName, OnActionExpandListener onActionExpandListener) {
LocaleCollectorBase localePickerController;
if (TextUtils.isEmpty(appPackageName)) {
- localePickerController = new SystemLocaleCollector(context);
+ localePickerController = new SystemLocaleCollector(context, explicitLocales);
} else {
localePickerController = new AppLocaleCollector(context, appPackageName);
}
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index d2eee91..bcff907 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -22,6 +22,7 @@
import android.os.LocaleList;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
@@ -29,6 +30,7 @@
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllformedLocaleException;
@@ -106,6 +108,9 @@
return mParent;
}
+ /**
+ * TODO: This method may rename to be more generic i.e. toLanguageTag().
+ */
@UnsupportedAppUsage
public String getId() {
return mId;
@@ -456,11 +461,30 @@
@UnsupportedAppUsage
public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
LocaleInfo parent, boolean translatedOnly) {
+ return getLevelLocales(context, ignorables, parent, translatedOnly, null);
+ }
+
+ /**
+ * @param explicitLocales Indicates only the locales within this list should be shown in the
+ * locale picker.
+ *
+ * Returns a list of locales for language or region selection.
+ * If the parent is null, then it is the language list.
+ * If it is not null, then the list will contain all the locales that belong to that parent.
+ * Example: if the parent is "ar", then the region list will contain all Arabic locales.
+ * (this is not language based, but language-script, so that it works for zh-Hant and so on.
+ */
+ public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
+ LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
fillCache(context);
String parentId = parent == null ? null : parent.getId();
-
HashSet<LocaleInfo> result = new HashSet<>();
- for (LocaleStore.LocaleInfo li : sLocaleCache.values()) {
+ HashMap<String, LocaleInfo> supportedLcoaleInfos =
+ explicitLocales == null
+ ? sLocaleCache
+ : convertExplicitLocales(explicitLocales, sLocaleCache.values());
+
+ for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
int level = getLevel(ignorables, li, translatedOnly);
if (level == 2) {
if (parent != null) { // region selection
@@ -479,6 +503,61 @@
return result;
}
+ /** Converts string array of explicit locales to HashMap */
+ public static HashMap<String, LocaleInfo> convertExplicitLocales(
+ LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+ // Trys to find the matched locale within android supported locales. If there is no matched
+ // locale, it will still keep the unsupported lcoale in list.
+ // Note: This currently does not support unicode extension check.
+ LocaleList localeList = matchLocaleFromSupportedLocaleList(
+ explicitLocales, localeinfo);
+
+ HashMap<String, LocaleInfo> localeInfos = new HashMap<>();
+ for (int i = 0; i < localeList.size(); i++) {
+ Locale locale = localeList.get(i);
+ if (locale.toString().isEmpty()) {
+ throw new IllformedLocaleException("Bad locale entry");
+ }
+
+ LocaleInfo li = new LocaleInfo(locale);
+ if (localeInfos.containsKey(li.getId())) {
+ continue;
+ }
+ localeInfos.put(li.getId(), li);
+ Locale parent = li.getParent();
+ if (parent != null) {
+ String parentId = parent.toLanguageTag();
+ if (!localeInfos.containsKey(parentId)) {
+ localeInfos.put(parentId, new LocaleInfo(parent));
+ }
+ }
+ }
+ return localeInfos;
+ }
+
+ private static LocaleList matchLocaleFromSupportedLocaleList(
+ LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+ //TODO: Adds a function for unicode extension if needed.
+ Locale[] resultLocales = new Locale[explicitLocales.size()];
+ for (int i = 0; i < explicitLocales.size(); i++) {
+ Locale locale = explicitLocales.get(i).stripExtensions();
+ if (!TextUtils.isEmpty(locale.getCountry())) {
+ for (LocaleInfo localeInfo :localeinfo) {
+ if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
+ && TextUtils.equals(locale.getCountry(),
+ localeInfo.getLocale().getCountry())) {
+ resultLocales[i] = localeInfo.getLocale();
+ continue;
+ }
+ }
+ }
+ if (resultLocales[i] == null) {
+ resultLocales[i] = locale;
+ }
+ }
+ return new LocaleList(resultLocales);
+ }
+
@UnsupportedAppUsage
public static LocaleInfo getLocaleInfo(Locale locale) {
String id = locale.toLanguageTag();
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ddfc238..992e243 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1617,6 +1617,14 @@
@VisibleForTesting
protected ResolverListController createListController(UserHandle userHandle) {
UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
+ ResolverRankerServiceResolverComparator resolverComparator =
+ new ResolverRankerServiceResolverComparator(
+ this,
+ getTargetIntent(),
+ getReferrerPackageName(),
+ null,
+ null,
+ getResolverRankerServiceUserHandleList(userHandle));
return new ResolverListController(
this,
mPm,
@@ -1624,6 +1632,7 @@
getReferrerPackageName(),
mLaunchedFromUid,
userHandle,
+ resolverComparator,
queryIntentsUser);
}
@@ -2534,4 +2543,21 @@
UserHandle predictedHandle) {
return resolveInfo.userHandle;
}
+
+ /**
+ * Returns the {@link List} of {@link UserHandle} to pass on to the
+ * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
+ */
+ protected final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
+ List<UserHandle> userList = new ArrayList<>();
+ userList.add(userHandle);
+ // Add clonedProfileUserHandle to the list only if we are:
+ // a. Building the Personal Tab.
+ // b. CloneProfile exists on the device.
+ if (userHandle.equals(getPersonalProfileUserHandle())
+ && getCloneProfileUserHandle() != null) {
+ userList.add(getCloneProfileUserHandle());
+ }
+ return userList;
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverComparatorModel.java b/core/java/com/android/internal/app/ResolverComparatorModel.java
index 3e8f64b..a390016 100644
--- a/core/java/com/android/internal/app/ResolverComparatorModel.java
+++ b/core/java/com/android/internal/app/ResolverComparatorModel.java
@@ -16,11 +16,11 @@
package com.android.internal.app;
-import android.content.ComponentName;
import android.content.pm.ResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
import java.util.Comparator;
-import java.util.List;
/**
* A ranking model for resolver targets, providing ordering and (optionally) numerical scoring.
@@ -45,7 +45,7 @@
* likelihood that the user will select that component as the target. Implementations that don't
* assign numerical scores are <em>recommended</em> to return a value of 0 for all components.
*/
- float getScore(ComponentName name);
+ float getScore(TargetInfo targetInfo);
/**
* Notify the model that the user selected a target. (Models may log this information, use it as
@@ -53,5 +53,5 @@
* {@code ResolverComparatorModel} instance is immutable, clients will need to get an up-to-date
* instance in order to see any changes in the ranking that might result from this feedback.
*/
- void notifyOnTargetSelected(ComponentName componentName);
+ void notifyOnTargetSelected(TargetInfo targetInfo);
}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index e3236ba..2df2b2b 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
@@ -157,17 +156,17 @@
/**
* Returns the app share score of the given {@code componentName}.
*/
- public float getScore(ComponentName componentName) {
- return mResolverListController.getScore(componentName);
+ public float getScore(TargetInfo targetInfo) {
+ return mResolverListController.getScore(targetInfo);
}
- public void updateModel(ComponentName componentName) {
- mResolverListController.updateModel(componentName);
+ public void updateModel(TargetInfo targetInfo) {
+ mResolverListController.updateModel(targetInfo);
}
- public void updateChooserCounts(String packageName, String action) {
+ public void updateChooserCounts(String packageName, String action, UserHandle userHandle) {
mResolverListController.updateChooserCounts(
- packageName, getUserHandle().getIdentifier(), action);
+ packageName, userHandle, action);
}
List<ResolvedComponentInfo> getUnfilteredResolveList() {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 5f90e73..d9a19b0 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -33,6 +33,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
import java.util.ArrayList;
import java.util.Collections;
@@ -72,7 +73,13 @@
UserHandle queryIntentsAsUser) {
this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle,
new ResolverRankerServiceResolverComparator(
- context, targetIntent, referrerPackage, null, null), queryIntentsAsUser);
+ context,
+ targetIntent,
+ referrerPackage,
+ null,
+ null,
+ userHandle),
+ queryIntentsAsUser);
}
public ResolverListController(
@@ -397,22 +404,22 @@
@VisibleForTesting
public float getScore(DisplayResolveInfo target) {
- return mResolverComparator.getScore(target.getResolvedComponentName());
+ return mResolverComparator.getScore(target);
}
/**
* Returns the app share score of the given {@code componentName}.
*/
- public float getScore(ComponentName componentName) {
- return mResolverComparator.getScore(componentName);
+ public float getScore(TargetInfo targetInfo) {
+ return mResolverComparator.getScore(targetInfo);
}
- public void updateModel(ComponentName componentName) {
- mResolverComparator.updateModel(componentName);
+ public void updateModel(TargetInfo targetInfo) {
+ mResolverComparator.updateModel(targetInfo);
}
- public void updateChooserCounts(String packageName, int userId, String action) {
- mResolverComparator.updateChooserCounts(packageName, userId, action);
+ public void updateChooserCounts(String packageName, UserHandle user, String action) {
+ mResolverComparator.updateChooserCounts(packageName, user, action);
}
public void destroy() {
diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
index e7f80a7..78c453d 100644
--- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
@@ -17,11 +17,13 @@
package com.android.internal.app;
+import android.annotation.Nullable;
import android.app.usage.UsageStats;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -38,12 +40,16 @@
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.google.android.collect.Lists;
+
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -69,10 +75,10 @@
private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
private final Collator mCollator;
- private final Map<String, UsageStats> mStats;
+ private final Map<UserHandle, Map<String, UsageStats>> mStatsPerUser;
private final long mCurrentTime;
private final long mSinceTime;
- private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>();
+ private final Map<UserHandle, LinkedHashMap<ComponentName, ResolverTarget>> mTargetsDictPerUser;
private final String mReferrerPackage;
private final Object mLock = new Object();
private ArrayList<ResolverTarget> mTargets;
@@ -85,17 +91,34 @@
private CountDownLatch mConnectSignal;
private ResolverRankerServiceComparatorModel mComparatorModel;
- public ResolverRankerServiceResolverComparator(Context context, Intent intent,
+ // context here refers to the activity calling this comparator.
+ // targetUserSpace refers to the userSpace in which the targets to be ranked lie.
+ public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent,
String referrerPackage, AfterCompute afterCompute,
- ChooserActivityLogger chooserActivityLogger) {
- super(context, intent);
- mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
- mReferrerPackage = referrerPackage;
- mContext = context;
+ ChooserActivityLogger chooserActivityLogger, UserHandle targetUserSpace) {
+ this(launchedFromContext, intent, referrerPackage, afterCompute, chooserActivityLogger,
+ Lists.newArrayList(targetUserSpace));
+ }
+ // context here refers to the activity calling this comparator.
+ // targetUserSpaceList refers to the userSpace(s) in which the targets to be ranked lie.
+ public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent,
+ String referrerPackage, AfterCompute afterCompute,
+ ChooserActivityLogger chooserActivityLogger, List<UserHandle> targetUserSpaceList) {
+ super(launchedFromContext, intent, targetUserSpaceList);
+ mCollator = Collator.getInstance(launchedFromContext
+ .getResources().getConfiguration().locale);
+ mReferrerPackage = referrerPackage;
+ mContext = launchedFromContext;
mCurrentTime = System.currentTimeMillis();
mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
- mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
+ mStatsPerUser = new HashMap<>();
+ mTargetsDictPerUser = new HashMap<>();
+ for (UserHandle user : targetUserSpaceList) {
+ mStatsPerUser.put(user, mUsmMap.get(user)
+ .queryAndAggregateUsageStats(mSinceTime, mCurrentTime));
+ mTargetsDictPerUser.put(user, new LinkedHashMap<>());
+ }
mAction = intent.getAction();
mRankerServiceName = new ComponentName(mContext, this.getClass());
setCallBack(afterCompute);
@@ -147,57 +170,63 @@
for (ResolvedComponentInfo target : targets) {
final ResolverTarget resolverTarget = new ResolverTarget();
- mTargetsDict.put(target.name, resolverTarget);
- final UsageStats pkStats = mStats.get(target.name.getPackageName());
- if (pkStats != null) {
- // Only count recency for apps that weren't the caller
- // since the caller is always the most recent.
- // Persistent processes muck this up, so omit them too.
- if (!target.name.getPackageName().equals(mReferrerPackage)
- && !isPersistentProcess(target)) {
- final float recencyScore =
- (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
- resolverTarget.setRecencyScore(recencyScore);
- if (recencyScore > mostRecencyScore) {
- mostRecencyScore = recencyScore;
- }
- }
- final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
- resolverTarget.setTimeSpentScore(timeSpentScore);
- if (timeSpentScore > mostTimeSpentScore) {
- mostTimeSpentScore = timeSpentScore;
- }
- final float launchScore = (float) pkStats.mLaunchCount;
- resolverTarget.setLaunchScore(launchScore);
- if (launchScore > mostLaunchScore) {
- mostLaunchScore = launchScore;
- }
-
- float chooserScore = 0.0f;
- if (pkStats.mChooserCounts != null && mAction != null
- && pkStats.mChooserCounts.get(mAction) != null) {
- chooserScore = (float) pkStats.mChooserCounts.get(mAction)
- .getOrDefault(mContentType, 0);
- if (mAnnotations != null) {
- final int size = mAnnotations.length;
- for (int i = 0; i < size; i++) {
- chooserScore += (float) pkStats.mChooserCounts.get(mAction)
- .getOrDefault(mAnnotations[i], 0);
+ final LinkedHashMap<ComponentName, ResolverTarget> targetsDict = mTargetsDictPerUser
+ .get(target.getResolveInfoAt(0).userHandle);
+ final Map<String, UsageStats> stats = mStatsPerUser
+ .get(target.getResolveInfoAt(0).userHandle);
+ if (targetsDict != null && stats != null) {
+ targetsDict.put(target.name, resolverTarget);
+ final UsageStats pkStats = stats.get(target.name.getPackageName());
+ if (pkStats != null) {
+ // Only count recency for apps that weren't the caller
+ // since the caller is always the most recent.
+ // Persistent processes muck this up, so omit them too.
+ if (!target.name.getPackageName().equals(mReferrerPackage)
+ && !isPersistentProcess(target)) {
+ final float recencyScore =
+ (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
+ resolverTarget.setRecencyScore(recencyScore);
+ if (recencyScore > mostRecencyScore) {
+ mostRecencyScore = recencyScore;
}
}
- }
- if (DEBUG) {
- if (mAction == null) {
- Log.d(TAG, "Action type is null");
- } else {
- Log.d(TAG, "Chooser Count of " + mAction + ":" +
- target.name.getPackageName() + " is " +
- Float.toString(chooserScore));
+ final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
+ resolverTarget.setTimeSpentScore(timeSpentScore);
+ if (timeSpentScore > mostTimeSpentScore) {
+ mostTimeSpentScore = timeSpentScore;
}
- }
- resolverTarget.setChooserScore(chooserScore);
- if (chooserScore > mostChooserScore) {
- mostChooserScore = chooserScore;
+ final float launchScore = (float) pkStats.mLaunchCount;
+ resolverTarget.setLaunchScore(launchScore);
+ if (launchScore > mostLaunchScore) {
+ mostLaunchScore = launchScore;
+ }
+
+ float chooserScore = 0.0f;
+ if (pkStats.mChooserCounts != null && mAction != null
+ && pkStats.mChooserCounts.get(mAction) != null) {
+ chooserScore = (float) pkStats.mChooserCounts.get(mAction)
+ .getOrDefault(mContentType, 0);
+ if (mAnnotations != null) {
+ final int size = mAnnotations.length;
+ for (int i = 0; i < size; i++) {
+ chooserScore += (float) pkStats.mChooserCounts.get(mAction)
+ .getOrDefault(mAnnotations[i], 0);
+ }
+ }
+ }
+ if (DEBUG) {
+ if (mAction == null) {
+ Log.d(TAG, "Action type is null");
+ } else {
+ Log.d(TAG, "Chooser Count of " + mAction + ":"
+ + target.name.getPackageName() + " is "
+ + Float.toString(chooserScore));
+ }
+ }
+ resolverTarget.setChooserScore(chooserScore);
+ if (chooserScore > mostChooserScore) {
+ mostChooserScore = chooserScore;
+ }
}
}
}
@@ -209,7 +238,11 @@
+ " mostChooserScore: " + mostChooserScore);
}
- mTargets = new ArrayList<>(mTargetsDict.values());
+ mTargets = new ArrayList<>();
+ for (UserHandle u : mTargetsDictPerUser.keySet()) {
+ mTargets.addAll(mTargetsDictPerUser.get(u).values());
+ }
+
for (ResolverTarget target : mTargets) {
final float recency = target.getRecencyScore() / mostRecencyScore;
setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
@@ -232,15 +265,15 @@
}
@Override
- public float getScore(ComponentName name) {
- return mComparatorModel.getScore(name);
+ public float getScore(TargetInfo targetInfo) {
+ return mComparatorModel.getScore(targetInfo);
}
// update ranking model when the connection to it is valid.
@Override
- public void updateModel(ComponentName componentName) {
+ public void updateModel(TargetInfo targetInfo) {
synchronized (mLock) {
- mComparatorModel.notifyOnTargetSelected(componentName);
+ mComparatorModel.notifyOnTargetSelected(targetInfo);
}
}
@@ -281,7 +314,8 @@
// resolve the service for ranking.
private Intent resolveRankerService() {
Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE);
- final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0);
+ final List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+ .queryIntentServices(intent, 0);
for (ResolveInfo resolveInfo : resolveInfos) {
if (resolveInfo == null || resolveInfo.serviceInfo == null
|| resolveInfo.serviceInfo.applicationInfo == null) {
@@ -294,7 +328,8 @@
resolveInfo.serviceInfo.applicationInfo.packageName,
resolveInfo.serviceInfo.name);
try {
- final String perm = mPm.getServiceInfo(componentName, 0).permission;
+ final String perm = mContext.getPackageManager()
+ .getServiceInfo(componentName, 0).permission;
if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) {
Log.w(TAG, "ResolverRankerService " + componentName + " does not require"
+ " permission " + ResolverRankerService.BIND_PERMISSION
@@ -305,9 +340,9 @@
+ " in the manifest.");
continue;
}
- if (PackageManager.PERMISSION_GRANTED != mPm.checkPermission(
- ResolverRankerService.HOLD_PERMISSION,
- resolveInfo.serviceInfo.packageName)) {
+ if (PackageManager.PERMISSION_GRANTED != mContext.getPackageManager()
+ .checkPermission(ResolverRankerService.HOLD_PERMISSION,
+ resolveInfo.serviceInfo.packageName)) {
Log.w(TAG, "ResolverRankerService " + componentName + " does not hold"
+ " permission " + ResolverRankerService.HOLD_PERMISSION
+ " - this service will not be queried for "
@@ -385,7 +420,9 @@
@Override
void beforeCompute() {
super.beforeCompute();
- mTargetsDict.clear();
+ for (UserHandle userHandle : mTargetsDictPerUser.keySet()) {
+ mTargetsDictPerUser.get(userHandle).clear();
+ }
mTargets = null;
mRankerServiceName = new ComponentName(mContext, this.getClass());
mComparatorModel = buildUpdatedModel();
@@ -465,14 +502,14 @@
// so the ResolverComparatorModel may provide inconsistent results. We should make immutable
// copies of the data (waiting for any necessary remaining data before creating the model).
return new ResolverRankerServiceComparatorModel(
- mStats,
- mTargetsDict,
+ mStatsPerUser,
+ mTargetsDictPerUser,
mTargets,
mCollator,
mRanker,
mRankerServiceName,
(mAnnotations != null),
- mPm);
+ mPmMap);
}
/**
@@ -481,35 +518,36 @@
* removing the complex legacy API.
*/
static class ResolverRankerServiceComparatorModel implements ResolverComparatorModel {
- private final Map<String, UsageStats> mStats; // Treat as immutable.
- private final Map<ComponentName, ResolverTarget> mTargetsDict; // Treat as immutable.
+ private final Map<UserHandle, Map<String, UsageStats>> mStatsPerUser; // Treat as immutable.
+ private final Map<UserHandle, LinkedHashMap<ComponentName,
+ ResolverTarget>> mTargetsDictPerUser; // Treat as immutable.
private final List<ResolverTarget> mTargets; // Treat as immutable.
private final Collator mCollator;
private final IResolverRankerService mRanker;
private final ComponentName mRankerServiceName;
private final boolean mAnnotationsUsed;
- private final PackageManager mPm;
+ private final Map<UserHandle, PackageManager> mPmMap;
// TODO: it doesn't look like we should have to pass both targets and targetsDict, but it's
// not written in a way that makes it clear whether we can derive one from the other (at
// least in this constructor).
ResolverRankerServiceComparatorModel(
- Map<String, UsageStats> stats,
- Map<ComponentName, ResolverTarget> targetsDict,
+ Map<UserHandle, Map<String, UsageStats>> statsPerUser,
+ Map<UserHandle, LinkedHashMap<ComponentName, ResolverTarget>> targetsDictPerUser,
List<ResolverTarget> targets,
Collator collator,
IResolverRankerService ranker,
ComponentName rankerServiceName,
boolean annotationsUsed,
- PackageManager pm) {
- mStats = stats;
- mTargetsDict = targetsDict;
+ Map<UserHandle, PackageManager> pmMap) {
+ mStatsPerUser = statsPerUser;
+ mTargetsDictPerUser = targetsDictPerUser;
mTargets = targets;
mCollator = collator;
mRanker = ranker;
mRankerServiceName = rankerServiceName;
mAnnotationsUsed = annotationsUsed;
- mPm = pm;
+ mPmMap = pmMap;
}
@Override
@@ -518,25 +556,29 @@
// a bug there, or do we have a way of knowing it will be non-null under certain
// conditions?
return (lhs, rhs) -> {
- if (mStats != null) {
- final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
- lhs.activityInfo.packageName, lhs.activityInfo.name));
- final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
- rhs.activityInfo.packageName, rhs.activityInfo.name));
+ final ResolverTarget lhsTarget = getActivityResolverTargetForUser(lhs.activityInfo,
+ lhs.userHandle);
+ final ResolverTarget rhsTarget = getActivityResolverTargetForUser(rhs.activityInfo,
+ rhs.userHandle);
- if (lhsTarget != null && rhsTarget != null) {
- final int selectProbabilityDiff = Float.compare(
- rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
+ if (lhsTarget != null && rhsTarget != null) {
+ final int selectProbabilityDiff = Float.compare(
+ rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
- if (selectProbabilityDiff != 0) {
- return selectProbabilityDiff > 0 ? 1 : -1;
- }
+ if (selectProbabilityDiff != 0) {
+ return selectProbabilityDiff > 0 ? 1 : -1;
}
}
- CharSequence sa = lhs.loadLabel(mPm);
+ CharSequence sa = null;
+ if (mPmMap.containsKey(lhs.userHandle)) {
+ sa = lhs.loadLabel(mPmMap.get(lhs.userHandle));
+ }
if (sa == null) sa = lhs.activityInfo.name;
- CharSequence sb = rhs.loadLabel(mPm);
+ CharSequence sb = null;
+ if (mPmMap.containsKey(rhs.userHandle)) {
+ sb = rhs.loadLabel(mPmMap.get(rhs.userHandle));
+ }
if (sb == null) sb = rhs.activityInfo.name;
return mCollator.compare(sa.toString().trim(), sb.toString().trim());
@@ -544,22 +586,28 @@
}
@Override
- public float getScore(ComponentName name) {
- final ResolverTarget target = mTargetsDict.get(name);
- if (target != null) {
- return target.getSelectProbability();
+ public float getScore(TargetInfo targetInfo) {
+ if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)
+ && mTargetsDictPerUser.get(targetInfo.getResolveInfo().userHandle)
+ .get(targetInfo.getResolvedComponentName()) != null) {
+ return mTargetsDictPerUser.get(targetInfo.getResolveInfo().userHandle)
+ .get(targetInfo.getResolvedComponentName()).getSelectProbability();
}
return 0;
}
@Override
- public void notifyOnTargetSelected(ComponentName componentName) {
+ public void notifyOnTargetSelected(TargetInfo targetInfo) {
if (mRanker != null) {
try {
- int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
- .indexOf(componentName);
+ int selectedPos = -1;
+ if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)) {
+ selectedPos = new ArrayList<>(mTargetsDictPerUser
+ .get(targetInfo.getResolveInfo().userHandle).keySet())
+ .indexOf(targetInfo.getResolvedComponentName());
+ }
if (selectedPos >= 0 && mTargets != null) {
- final float selectedProbability = getScore(componentName);
+ final float selectedProbability = getScore(targetInfo);
int order = 0;
for (ResolverTarget target : mTargets) {
if (target.getSelectProbability() > selectedProbability) {
@@ -570,7 +618,8 @@
mRanker.train(mTargets, selectedPos);
} else {
if (DEBUG) {
- Log.d(TAG, "Selected a unknown component: " + componentName);
+ Log.d(TAG, "Selected a unknown component: " + targetInfo
+ .getResolvedComponentName());
}
}
} catch (RemoteException e) {
@@ -594,5 +643,16 @@
metricsLogger.write(log);
}
}
+
+ @Nullable
+ private ResolverTarget getActivityResolverTargetForUser(
+ ActivityInfo activity, UserHandle user) {
+ if ((mStatsPerUser == null) || !mTargetsDictPerUser.containsKey(user)) {
+ return null;
+ }
+ return mTargetsDictPerUser
+ .get(user)
+ .get(new ComponentName(activity.packageName, activity.name));
+ }
}
}
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
index 9a6d4c1..416f510 100644
--- a/core/java/com/android/internal/app/SystemLocaleCollector.java
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -26,9 +26,15 @@
/** The Locale data collector for System language. */
class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
private final Context mContext;
+ private LocaleList mExplicitLocales;
SystemLocaleCollector(Context context) {
+ this(context, null);
+ }
+
+ SystemLocaleCollector(Context context, LocaleList explicitLocales) {
mContext = context;
+ mExplicitLocales = explicitLocales;
}
@Override
@@ -47,18 +53,16 @@
boolean translatedOnly, boolean isForCountryMode) {
Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
Set<LocaleStore.LocaleInfo> localeList;
-
if (isForCountryMode) {
localeList = LocaleStore.getLevelLocales(mContext,
- langTagsToIgnore, parent, translatedOnly);
+ langTagsToIgnore, parent, translatedOnly, mExplicitLocales);
} else {
localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
- null /* no parent */, translatedOnly);
+ null /* no parent */, translatedOnly, mExplicitLocales);
}
return localeList;
}
-
@Override
public boolean hasSpecificPackageName() {
return false;
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 72b9cd2..818a503 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -73,6 +73,7 @@
import java.io.PrintWriter;
import java.util.Comparator;
+import java.util.concurrent.TimeUnit;
public final class ProcessState {
private static final String TAG = "ProcessStats";
@@ -1542,6 +1543,75 @@
proto.write(fieldId, procName);
}
+ /** Dumps the duration of each state to statsEventOutput. */
+ public void dumpStateDurationToStatsd(
+ int atomTag, ProcessStats processStats, StatsEventOutput statsEventOutput) {
+ long topMs = 0;
+ long fgsMs = 0;
+ long boundTopMs = 0;
+ long boundFgsMs = 0;
+ long importantForegroundMs = 0;
+ long cachedMs = 0;
+ long frozenMs = 0;
+ long otherMs = 0;
+ for (int i = 0, size = mDurations.getKeyCount(); i < size; i++) {
+ final int key = mDurations.getKeyAt(i);
+ final int type = SparseMappingTable.getIdFromKey(key);
+ int procStateIndex = type % STATE_COUNT;
+ long duration = mDurations.getValue(key);
+ switch (procStateIndex) {
+ case STATE_TOP:
+ topMs += duration;
+ break;
+ case STATE_BOUND_TOP_OR_FGS:
+ boundTopMs += duration;
+ break;
+ case STATE_FGS:
+ fgsMs += duration;
+ break;
+ case STATE_IMPORTANT_FOREGROUND:
+ case STATE_IMPORTANT_BACKGROUND:
+ importantForegroundMs += duration;
+ break;
+ case STATE_BACKUP:
+ case STATE_SERVICE:
+ case STATE_SERVICE_RESTARTING:
+ case STATE_RECEIVER:
+ case STATE_HEAVY_WEIGHT:
+ case STATE_HOME:
+ case STATE_LAST_ACTIVITY:
+ case STATE_PERSISTENT:
+ otherMs += duration;
+ break;
+ case STATE_CACHED_ACTIVITY:
+ case STATE_CACHED_ACTIVITY_CLIENT:
+ case STATE_CACHED_EMPTY:
+ cachedMs += duration;
+ break;
+ // TODO (b/261910877) Add support for tracking boundFgsMs and
+ // frozenMs.
+ }
+ }
+ statsEventOutput.write(
+ atomTag,
+ getUid(),
+ getName(),
+ (int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodEndUptime),
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ processStats.mTimePeriodEndUptime
+ - processStats.mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(topMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(fgsMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(boundTopMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(boundFgsMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(importantForegroundMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(cachedMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(frozenMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(otherMs));
+ }
+
/** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */
public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto, long fieldId,
String procName, int uid, long now,
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index d2b2f0a..f3ed09a 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -43,6 +43,7 @@
import com.android.internal.app.ProcessMap;
import com.android.internal.app.procstats.AssociationState.SourceKey;
import com.android.internal.app.procstats.AssociationState.SourceState;
+import com.android.internal.util.function.QuintConsumer;
import dalvik.system.VMRuntime;
@@ -56,6 +57,8 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -2389,6 +2392,79 @@
}
}
+ void forEachProcess(Consumer<ProcessState> consumer) {
+ final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+ for (int ip = 0, size = procMap.size(); ip < size; ip++) {
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
+ final ProcessState processState = uids.valueAt(iu);
+ consumer.accept(processState);
+ }
+ }
+ }
+
+ void forEachAssociation(
+ QuintConsumer<AssociationState, Integer, String, SourceKey, SourceState> consumer) {
+ final ArrayMap<String, SparseArray<LongSparseArray<PackageState>>> pkgMap =
+ mPackages.getMap();
+ for (int ip = 0, size = pkgMap.size(); ip < size; ip++) {
+ final SparseArray<LongSparseArray<PackageState>> uids = pkgMap.valueAt(ip);
+ for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
+ final int uid = uids.keyAt(iu);
+ final LongSparseArray<PackageState> versions = uids.valueAt(iu);
+ for (int iv = 0, versionsSize = versions.size(); iv < versionsSize; iv++) {
+ final PackageState state = versions.valueAt(iv);
+ for (int iasc = 0, ascSize = state.mAssociations.size();
+ iasc < ascSize;
+ iasc++) {
+ final String serviceName = state.mAssociations.keyAt(iasc);
+ final AssociationState asc = state.mAssociations.valueAt(iasc);
+ for (int is = 0, sourcesSize = asc.mSources.size();
+ is < sourcesSize;
+ is++) {
+ final SourceState src = asc.mSources.valueAt(is);
+ final SourceKey key = asc.mSources.keyAt(is);
+ consumer.accept(asc, uid, serviceName, key, src);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Dumps the stats of all processes to statsEventOutput. */
+ public void dumpProcessState(int atomTag, StatsEventOutput statsEventOutput) {
+ forEachProcess(
+ (processState) -> {
+ if (processState.isMultiPackage()
+ && processState.getCommonProcess() != processState) {
+ return;
+ }
+ processState.dumpStateDurationToStatsd(atomTag, this, statsEventOutput);
+ });
+ }
+
+ /** Dumps all process association data to statsEventOutput. */
+ public void dumpProcessAssociation(int atomTag, StatsEventOutput statsEventOutput) {
+ forEachAssociation(
+ (asc, serviceUid, serviceName, key, src) -> {
+ statsEventOutput.write(
+ atomTag,
+ key.mUid,
+ key.mProcess,
+ serviceUid,
+ serviceName,
+ (int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodEndUptime),
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ mTimePeriodEndUptime - mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(src.mDuration),
+ src.mActiveCount,
+ asc.getProcessName());
+ });
+ }
+
private void dumpProtoPreamble(ProtoOutputStream proto) {
proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
diff --git a/core/java/com/android/internal/app/procstats/StatsEventOutput.java b/core/java/com/android/internal/app/procstats/StatsEventOutput.java
new file mode 100644
index 0000000..b2e4054
--- /dev/null
+++ b/core/java/com/android/internal/app/procstats/StatsEventOutput.java
@@ -0,0 +1,98 @@
+/*
+ * 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.internal.app.procstats;
+
+import android.util.StatsEvent;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+
+/**
+ * A simple wrapper of FrameworkStatsLog.buildStatsEvent. This allows unit tests to mock out the
+ * dependency.
+ */
+public class StatsEventOutput {
+
+ List<StatsEvent> mOutput;
+
+ public StatsEventOutput(List<StatsEvent> output) {
+ mOutput = output;
+ }
+
+ /** Writes the data to the output. */
+ public void write(
+ int atomTag,
+ int uid,
+ String processName,
+ int measurementStartUptimeSecs,
+ int measurementEndUptimeSecs,
+ int measurementDurationUptimeSecs,
+ int topSeconds,
+ int fgsSeconds,
+ int boundTopSeconds,
+ int boundFgsSeconds,
+ int importantForegroundSeconds,
+ int cachedSeconds,
+ int frozenSeconds,
+ int otherSeconds) {
+ mOutput.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ uid,
+ processName,
+ measurementStartUptimeSecs,
+ measurementEndUptimeSecs,
+ measurementDurationUptimeSecs,
+ topSeconds,
+ fgsSeconds,
+ boundTopSeconds,
+ boundFgsSeconds,
+ importantForegroundSeconds,
+ cachedSeconds,
+ frozenSeconds,
+ otherSeconds));
+ }
+
+ /** Writes the data to the output. */
+ public void write(
+ int atomTag,
+ int clientUid,
+ String processName,
+ int serviceUid,
+ String serviceName,
+ int measurementStartUptimeSecs,
+ int measurementEndUptimeSecs,
+ int measurementDurationUptimeSecs,
+ int activeDurationUptimeSecs,
+ int activeCount,
+ String serviceProcessName) {
+ mOutput.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ clientUid,
+ processName,
+ serviceUid,
+ serviceName,
+ measurementStartUptimeSecs,
+ measurementEndUptimeSecs,
+ measurementDurationUptimeSecs,
+ activeDurationUptimeSecs,
+ activeCount,
+ serviceProcessName));
+ }
+}
diff --git a/core/java/com/android/internal/content/InstallLocationUtils.java b/core/java/com/android/internal/content/InstallLocationUtils.java
index 4d9c09e..a173ce1 100644
--- a/core/java/com/android/internal/content/InstallLocationUtils.java
+++ b/core/java/com/android/internal/content/InstallLocationUtils.java
@@ -292,7 +292,7 @@
// For new installations of a predefined size, check property to let it through
// regardless of the actual free space.
- if (bestCandidate != null && Integer.MAX_VALUE == params.sizeBytes
+ if (!volumePaths.isEmpty() && Integer.MAX_VALUE == params.sizeBytes
&& SystemProperties.getBoolean("debug.pm.install_skip_size_check_for_maxint",
false)) {
return bestCandidate;
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 8690e8d..3020d77 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -44,6 +44,7 @@
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.InsertModeGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
@@ -314,6 +315,8 @@
result = mTextView.performHandwritingRemoveSpaceGesture((RemoveSpaceGesture) gesture);
} else if (gesture instanceof JoinOrSplitGesture) {
result = mTextView.performHandwritingJoinOrSplitGesture((JoinOrSplitGesture) gesture);
+ } else if (gesture instanceof InsertModeGesture) {
+ result = mTextView.performHandwritingInsertModeGesture((InsertModeGesture) gesture);
} else {
result = HANDWRITING_GESTURE_RESULT_UNSUPPORTED;
}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index af205d2..40d5c47 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -50,9 +50,6 @@
public static final boolean CONFIG_SMALL_BATTERY =
SystemProperties.getBoolean("ro.config.small_battery", false);
- // ------ ro.fw.* ------------ //
- public static final boolean FW_SYSTEM_USER_SPLIT =
- SystemProperties.getBoolean("ro.fw.system_user_split", false);
/**
* Indicates whether the device should run in headless system user mode,
* in which user 0 only runs the system, not a real user.
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 8f943ef..ad1fdba 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -79,7 +79,9 @@
Consts.TAG_WM),
WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
- WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
+ WM_DEBUG_WINDOW_TRANSITIONS_MIN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a704eb3..1c85ca2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -53,7 +53,7 @@
boolean showImeSwitcher);
void setWindowState(int display, int window, int state);
- void showRecentApps(boolean triggeredFromAltTab, boolean forward);
+ void showRecentApps(boolean triggeredFromAltTab);
void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecentApps();
void toggleSplitScreen();
diff --git a/core/java/com/android/internal/util/ObservableServiceConnection.java b/core/java/com/android/internal/util/ObservableServiceConnection.java
index 3165d29..45256fd 100644
--- a/core/java/com/android/internal/util/ObservableServiceConnection.java
+++ b/core/java/com/android/internal/util/ObservableServiceConnection.java
@@ -165,6 +165,13 @@
}
/**
+ * Executes code on the executor specified at construction.
+ */
+ public void execute(Runnable runnable) {
+ mExecutor.execute(runnable);
+ }
+
+ /**
* Initiate binding to the service.
*
* @return {@code true} if initiating binding succeed, {@code false} if the binding failed or
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 79c5196..3a393b6 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,7 @@
package com.android.internal.util;
import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -11,29 +11,18 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.graphics.Insets;
-import android.graphics.ParcelableColorSpace;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.WindowManager.ScreenshotSource;
-import android.view.WindowManager.ScreenshotType;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.Objects;
import java.util.function.Consumer;
public class ScreenshotHelper {
@@ -41,212 +30,6 @@
public static final int SCREENSHOT_MSG_URI = 1;
public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
- /**
- * Describes a screenshot request.
- */
- public static class ScreenshotRequest implements Parcelable {
- @ScreenshotType
- private final int mType;
-
- @ScreenshotSource
- private final int mSource;
-
- private final Bundle mBitmapBundle;
- private final Rect mBoundsInScreen;
- private final Insets mInsets;
- private final int mTaskId;
- private final int mUserId;
- private final ComponentName mTopComponent;
-
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
- this(type, source, /* topComponent */ null);
- }
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
- ComponentName topComponent) {
- this(type,
- source,
- /* bitmapBundle*/ null,
- /* boundsInScreen */ null,
- /* insets */ null,
- /* taskId */ -1,
- /* userId */ -1,
- topComponent);
- }
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
- Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
- ComponentName topComponent) {
- mType = type;
- mSource = source;
- mBitmapBundle = bitmapBundle;
- mBoundsInScreen = boundsInScreen;
- mInsets = insets;
- mTaskId = taskId;
- mUserId = userId;
- mTopComponent = topComponent;
- }
-
- ScreenshotRequest(Parcel in) {
- mType = in.readInt();
- mSource = in.readInt();
- if (in.readInt() == 1) {
- mBitmapBundle = in.readBundle(getClass().getClassLoader());
- mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
- mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
- mTaskId = in.readInt();
- mUserId = in.readInt();
- mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
- ComponentName.class);
- } else {
- mBitmapBundle = null;
- mBoundsInScreen = null;
- mInsets = null;
- mTaskId = -1;
- mUserId = -1;
- mTopComponent = null;
- }
- }
-
- @ScreenshotType
- public int getType() {
- return mType;
- }
-
- @ScreenshotSource
- public int getSource() {
- return mSource;
- }
-
- public Bundle getBitmapBundle() {
- return mBitmapBundle;
- }
-
- public Rect getBoundsInScreen() {
- return mBoundsInScreen;
- }
-
- public Insets getInsets() {
- return mInsets;
- }
-
- public int getTaskId() {
- return mTaskId;
- }
-
- public int getUserId() {
- return mUserId;
- }
-
- public ComponentName getTopComponent() {
- return mTopComponent;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mType);
- dest.writeInt(mSource);
- if (mBitmapBundle == null) {
- dest.writeInt(0);
- } else {
- dest.writeInt(1);
- dest.writeBundle(mBitmapBundle);
- dest.writeParcelable(mBoundsInScreen, 0);
- dest.writeParcelable(mInsets, 0);
- dest.writeInt(mTaskId);
- dest.writeInt(mUserId);
- dest.writeParcelable(mTopComponent, 0);
- }
- }
-
- @NonNull
- public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
- new Parcelable.Creator<ScreenshotRequest>() {
-
- @Override
- public ScreenshotRequest createFromParcel(Parcel source) {
- return new ScreenshotRequest(source);
- }
-
- @Override
- public ScreenshotRequest[] newArray(int size) {
- return new ScreenshotRequest[size];
- }
- };
- }
-
- /**
- * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
- * content. This is expected to be used together with {@link #provideScreenshot} to handle a
- * hardware bitmap as a screenshot.
- */
- public static final class HardwareBitmapBundler {
- private static final String KEY_BUFFER = "bitmap_util_buffer";
- private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
-
- private HardwareBitmapBundler() {
- }
-
- /**
- * Creates a Bundle that represents the given Bitmap.
- * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid
- * copies when passing across processes, only pass to processes you trust.
- *
- * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the
- * returned Bundle should be treated as a standalone object.
- *
- * @param bitmap to convert to bundle
- * @return a Bundle representing the bitmap, should only be parsed by
- * {@link #bundleToHardwareBitmap(Bundle)}
- */
- public static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
- if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
- throw new IllegalArgumentException(
- "Passed bitmap must have hardware config, found: " + bitmap.getConfig());
- }
-
- // Bitmap assumes SRGB for null color space
- ParcelableColorSpace colorSpace =
- bitmap.getColorSpace() == null
- ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
- : new ParcelableColorSpace(bitmap.getColorSpace());
-
- Bundle bundle = new Bundle();
- bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
- bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
-
- return bundle;
- }
-
- /**
- * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
- *
- * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
- * this Bitmap on to any other source.
- *
- * @param bundle containing the bitmap
- * @return a hardware Bitmap
- */
- public static Bitmap bundleToHardwareBitmap(Bundle bundle) {
- if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
- throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
- }
-
- HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
- ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
- ParcelableColorSpace.class);
-
- return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
- colorSpace.getColorSpace());
- }
- }
-
private static final String TAG = "ScreenshotHelper";
// Time until we give up on the screenshot & show an error instead.
@@ -277,20 +60,35 @@
/**
* Request a screenshot be taken.
* <p>
- * Added to support reducing unit test duration; the method variant without a timeout argument
- * is recommended for general use.
+ * Convenience method for taking a full screenshot with provided source.
*
- * @param type The type of screenshot, defined by {@link ScreenshotType}
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler used to process messages received from the screenshot service
+ * @param source source of the screenshot request, defined by {@link
+ * ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
* @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
+ * null if no screenshot was saved
*/
- public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
- @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
- takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
- completionConsumer);
+ public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest request =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build();
+ takeScreenshot(request, handler, completionConsumer);
+ }
+
+ /**
+ * Request a screenshot be taken.
+ * <p>
+ *
+ * @param request description of the screenshot request, either for taking a
+ * screenshot or
+ * providing a bitmap
+ * @param handler used to process messages received from the screenshot service
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
+ */
+ public void takeScreenshot(ScreenshotRequest request, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer) {
+ takeScreenshotInternal(request, handler, completionConsumer, SCREENSHOT_TIMEOUT_MS);
}
/**
@@ -299,46 +97,16 @@
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param type The type of screenshot, defined by {@link ScreenshotType}
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler used to process messages received from the screenshot service
- * @param timeoutMs time limit for processing, intended only for testing
+ * @param request description of the screenshot request, either for taking a
+ * screenshot or providing a bitmap
+ * @param handler used to process messages received from the screenshot service
+ * @param timeoutMs time limit for processing, intended only for testing
* @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
+ * null if no screenshot was saved
*/
@VisibleForTesting
- public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
- @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
- takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
- }
-
- /**
- * Request that provided image be handled as if it was a screenshot.
- *
- * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
- * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
- * @param insets The insets that the image was shown with, inside the screen bounds.
- * @param taskId The taskId of the task that the screen shot was taken of.
- * @param userId The userId of user running the task provided in taskId.
- * @param topComponent The component name of the top component running in the task.
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
- */
- public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
- @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
- @ScreenshotSource int source, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
- source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
- takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
- }
-
- private void takeScreenshot(@NonNull Handler handler,
- ScreenshotRequest screenshotRequest, long timeoutMs,
- @Nullable Consumer<Uri> completionConsumer) {
+ public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
synchronized (mScreenshotLock) {
final Runnable mScreenshotTimeout = () -> {
@@ -354,7 +122,7 @@
}
};
- Message msg = Message.obtain(null, 0, screenshotRequest);
+ Message msg = Message.obtain(null, 0, request);
Handler h = new Handler(handler.getLooper()) {
@Override
@@ -471,5 +239,4 @@
Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
}
-
}
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.aidl b/core/java/com/android/internal/util/ScreenshotRequest.aidl
new file mode 100644
index 0000000..b08905d
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.internal.util;
+
+parcelable ScreenshotRequest;
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java
new file mode 100644
index 0000000..1902f80
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.java
@@ -0,0 +1,332 @@
+/*
+ * 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.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.ParcelableColorSpace;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.Objects;
+
+/**
+ * Describes a screenshot request.
+ */
+public class ScreenshotRequest implements Parcelable {
+ private static final String TAG = "ScreenshotRequest";
+
+ @WindowManager.ScreenshotType
+ private final int mType;
+ @WindowManager.ScreenshotSource
+ private final int mSource;
+ private final ComponentName mTopComponent;
+ private final int mTaskId;
+ private final int mUserId;
+ private final Bitmap mBitmap;
+ private final Rect mBoundsInScreen;
+ private final Insets mInsets;
+
+ private ScreenshotRequest(
+ @WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source,
+ ComponentName topComponent, int taskId, int userId,
+ Bitmap bitmap, Rect boundsInScreen, Insets insets) {
+ mType = type;
+ mSource = source;
+ mTopComponent = topComponent;
+ mTaskId = taskId;
+ mUserId = userId;
+ mBitmap = bitmap;
+ mBoundsInScreen = boundsInScreen;
+ mInsets = insets;
+ }
+
+ ScreenshotRequest(Parcel in) {
+ mType = in.readInt();
+ mSource = in.readInt();
+ mTopComponent = in.readTypedObject(ComponentName.CREATOR);
+ mTaskId = in.readInt();
+ mUserId = in.readInt();
+ mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR));
+ mBoundsInScreen = in.readTypedObject(Rect.CREATOR);
+ mInsets = in.readTypedObject(Insets.CREATOR);
+ }
+
+ @WindowManager.ScreenshotType
+ public int getType() {
+ return mType;
+ }
+
+ @WindowManager.ScreenshotSource
+ public int getSource() {
+ return mSource;
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ public Rect getBoundsInScreen() {
+ return mBoundsInScreen;
+ }
+
+ public Insets getInsets() {
+ return mInsets;
+ }
+
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ComponentName getTopComponent() {
+ return mTopComponent;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mSource);
+ dest.writeTypedObject(mTopComponent, 0);
+ dest.writeInt(mTaskId);
+ dest.writeInt(mUserId);
+ dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0);
+ dest.writeTypedObject(mBoundsInScreen, 0);
+ dest.writeTypedObject(mInsets, 0);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
+ new Parcelable.Creator<ScreenshotRequest>() {
+
+ @Override
+ public ScreenshotRequest createFromParcel(Parcel source) {
+ return new ScreenshotRequest(source);
+ }
+
+ @Override
+ public ScreenshotRequest[] newArray(int size) {
+ return new ScreenshotRequest[size];
+ }
+ };
+
+ /**
+ * Builder class for {@link ScreenshotRequest} objects.
+ */
+ public static class Builder {
+ @WindowManager.ScreenshotType
+ private final int mType;
+
+ @WindowManager.ScreenshotSource
+ private final int mSource;
+
+ private Bitmap mBitmap;
+ private Rect mBoundsInScreen;
+ private Insets mInsets = Insets.NONE;
+ private int mTaskId = INVALID_TASK_ID;
+ private int mUserId = USER_NULL;
+ private ComponentName mTopComponent;
+
+ /**
+ * Begin building a ScreenshotRequest.
+ *
+ * @param type The type of the screenshot request, defined by {@link
+ * WindowManager.ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link
+ * WindowManager.ScreenshotSource}
+ */
+ public Builder(
+ @WindowManager.ScreenshotType int type,
+ @WindowManager.ScreenshotSource int source) {
+ mType = type;
+ mSource = source;
+ }
+
+ /**
+ * Construct a new {@link ScreenshotRequest} with the set parameters.
+ */
+ public ScreenshotRequest build() {
+ if (mType == TAKE_SCREENSHOT_FULLSCREEN && mBitmap != null) {
+ Log.w(TAG, "Bitmap provided, but request is fullscreen. Bitmap will be ignored.");
+ }
+ if (mType == TAKE_SCREENSHOT_PROVIDED_IMAGE && mBitmap == null) {
+ throw new IllegalStateException(
+ "Request is PROVIDED_IMAGE, but no bitmap is provided!");
+ }
+
+ return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap,
+ mBoundsInScreen, mInsets);
+ }
+
+ /**
+ * Set the top component associated with this request.
+ *
+ * @param topComponent The component name of the top component running in the task.
+ */
+ public Builder setTopComponent(ComponentName topComponent) {
+ mTopComponent = topComponent;
+ return this;
+ }
+
+ /**
+ * Set the task id associated with this request.
+ *
+ * @param taskId The taskId of the task that the screenshot was taken of.
+ */
+ public Builder setTaskId(int taskId) {
+ mTaskId = taskId;
+ return this;
+ }
+
+ /**
+ * Set the user id associated with this request.
+ *
+ * @param userId The userId of user running the task provided in taskId.
+ */
+ public Builder setUserId(int userId) {
+ mUserId = userId;
+ return this;
+ }
+
+ /**
+ * Set the bitmap associated with this request.
+ *
+ * @param bitmap The provided screenshot.
+ */
+ public Builder setBitmap(Bitmap bitmap) {
+ mBitmap = bitmap;
+ return this;
+ }
+
+ /**
+ * Set the bounds for the provided bitmap.
+ *
+ * @param bounds The bounds in screen coordinates that the bitmap originated from.
+ */
+ public Builder setBoundsOnScreen(Rect bounds) {
+ mBoundsInScreen = bounds;
+ return this;
+ }
+
+ /**
+ * Set the insets for the provided bitmap.
+ *
+ * @param insets The insets that the image was shown with, inside the screen bounds.
+ */
+ public Builder setInsets(@NonNull Insets insets) {
+ mInsets = insets;
+ return this;
+ }
+ }
+
+ /**
+ * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
+ * content. This is used together with a fully-defined ScreenshotRequest to handle a hardware
+ * bitmap as a screenshot.
+ */
+ private static final class HardwareBitmapBundler {
+ private static final String KEY_BUFFER = "bitmap_util_buffer";
+ private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
+
+ private HardwareBitmapBundler() {
+ }
+
+ /**
+ * Creates a Bundle that represents the given Bitmap.
+ * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will
+ * avoid
+ * copies when passing across processes, only pass to processes you trust.
+ *
+ * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions,
+ * the
+ * returned Bundle should be treated as a standalone object.
+ *
+ * @param bitmap to convert to bundle
+ * @return a Bundle representing the bitmap, should only be parsed by
+ * {@link #bundleToHardwareBitmap(Bundle)}
+ */
+ private static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
+ if (bitmap == null) {
+ return null;
+ }
+ if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+ throw new IllegalArgumentException(
+ "Passed bitmap must have hardware config, found: "
+ + bitmap.getConfig());
+ }
+
+ // Bitmap assumes SRGB for null color space
+ ParcelableColorSpace colorSpace =
+ bitmap.getColorSpace() == null
+ ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
+ : new ParcelableColorSpace(bitmap.getColorSpace());
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
+ bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
+
+ return bundle;
+ }
+
+ /**
+ * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)}.
+ *
+ * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful
+ * passing
+ * this Bitmap on to any other source.
+ *
+ * @param bundle containing the bitmap
+ * @return a hardware Bitmap
+ */
+ private static Bitmap bundleToHardwareBitmap(Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
+ throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
+ }
+
+ HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
+ ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
+ ParcelableColorSpace.class);
+
+ return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
+ colorSpace.getColorSpace());
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3c305f6..86fd956 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -619,12 +619,11 @@
}
boolean disabledByDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_disableLockscreenByDefault);
- boolean isSystemUser = UserManager.isSplitSystemUser() && userId == UserHandle.USER_SYSTEM;
UserInfo userInfo = getUserManager().getUserInfo(userId);
boolean isDemoUser = UserManager.isDeviceInDemoMode(mContext) && userInfo != null
&& userInfo.isDemo();
return getBoolean(DISABLE_LOCKSCREEN_KEY, false, userId)
- || (disabledByDefault && !isSystemUser)
+ || disabledByDefault
|| isDemoUser;
}
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index a68040d..bd85874 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -72,6 +72,7 @@
per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
per-file *HardwareBuffer* = file:/graphics/java/android/graphics/OWNERS
per-file android_hardware_SyncFence.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file android_hardware_OverlayProperties.cpp = file:/graphics/java/android/graphics/OWNERS
per-file android_os_GraphicsEnvironment.cpp = file:platform/frameworks/native:/opengl/OWNERS
### Text ###
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 7d379e5..0bc0878 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -68,6 +68,7 @@
}
const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
+ const auto usiVersion = deviceInfo.getUsiVersion().value_or(InputDeviceUsiVersion{-1, -1});
ScopedLocalRef<jobject>
inputDeviceObj(env,
@@ -81,7 +82,9 @@
keyboardLanguageTagObj.get(), keyboardLayoutTypeObj.get(),
deviceInfo.hasVibrator(), deviceInfo.hasMic(),
deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(),
- deviceInfo.hasBattery(), deviceInfo.supportsUsi()));
+ deviceInfo.hasBattery(), usiVersion.majorVersion,
+ usiVersion.minorVersion,
+ deviceInfo.getAssociatedDisplayId()));
// Note: We do not populate the Bluetooth address into the InputDevice object to avoid leaking
// it to apps that do not have the Bluetooth permission.
@@ -105,7 +108,7 @@
gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
"(IIILjava/lang/String;IILjava/lang/"
"String;ZIILandroid/view/KeyCharacterMap;Ljava/"
- "lang/String;Ljava/lang/String;ZZZZZZ)V");
+ "lang/String;Ljava/lang/String;ZZZZZIII)V");
gInputDeviceClassInfo.addMotionRange =
GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V");
diff --git a/core/proto/android/nfc/nfc_service.proto b/core/proto/android/nfc/nfc_service.proto
index 1dcd5cc..2df1d5d 100644
--- a/core/proto/android/nfc/nfc_service.proto
+++ b/core/proto/android/nfc/nfc_service.proto
@@ -60,7 +60,7 @@
optional bool secure_nfc_capable = 13;
optional bool vr_mode_enabled = 14;
optional DiscoveryParamsProto discovery_params = 15;
- reserved 16;
+ optional P2pLinkManagerProto p2p_link_manager = 16;
optional com.android.nfc.cardemulation.CardEmulationManagerProto card_emulation_manager = 17;
optional NfcDispatcherProto nfc_dispatcher = 18;
optional string native_crash_logs = 19 [(.android.privacy).dest = DEST_EXPLICIT];
@@ -77,6 +77,38 @@
optional bool enable_p2p = 5;
}
+// Debugging information for com.android.nfc.P2pLinkManager
+message P2pLinkManagerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ enum LinkState {
+ LINK_STATE_UNKNOWN = 0;
+ LINK_STATE_DOWN = 1;
+ LINK_STATE_DEBOUNCE = 2;
+ LINK_STATE_UP = 3;
+ }
+
+ enum SendState {
+ SEND_STATE_UNKNOWN = 0;
+ SEND_STATE_NOTHING_TO_SEND = 1;
+ SEND_STATE_NEED_CONFIRMATION = 2;
+ SEND_STATE_SENDING = 3;
+ SEND_STATE_COMPLETE = 4;
+ SEND_STATE_CANCELED = 5;
+ }
+
+ optional int32 default_miu = 1;
+ optional int32 default_rw_size = 2;
+ optional LinkState link_state = 3;
+ optional SendState send_state = 4;
+ optional int32 send_flags = 5;
+ optional bool send_enabled = 6;
+ optional bool receive_enabled = 7;
+ optional string callback_ndef = 8 [(.android.privacy).dest = DEST_EXPLICIT];
+ optional .android.nfc.NdefMessageProto message_to_send = 9;
+ repeated string uris_to_send = 10 [(.android.privacy).dest = DEST_EXPLICIT];
+}
+
// Debugging information for com.android.nfc.NfcDispatcher
message NfcDispatcherProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/core/res/res/drawable/pointer_handwriting_icon.xml b/core/res/res/drawable/pointer_handwriting_icon.xml
new file mode 100644
index 0000000..cdbf693
--- /dev/null
+++ b/core/res/res/drawable/pointer_handwriting_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ android:bitmap="@drawable/pointer_crosshair"
+ android:hotSpotX="12dp"
+ android:hotSpotY="12dp" />
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1b6f88f..826624a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3299,6 +3299,8 @@
<enum name="grab" value="1020" />
<!-- Pointer icon of a hand sign while grabbing something. -->
<enum name="grabbing" value="1021" />
+ <!-- Pointer icon indicating handwriting. -->
+ <enum name="handwriting" value="1022"/>
</attr>
<!-- Whether this view has elements that may overlap when drawn. See
@@ -3763,6 +3765,9 @@
{@link android.inputmethodservice.InputMethodService#onFinishInput()}.
-->
<attr name="supportsStylusHandwriting" format="boolean" />
+ <!-- Class name of an activity that allows the user to modify the stylus handwriting
+ settings for this service -->
+ <attr name="stylusHandwritingSettingsActivity" format="string" />
</declare-styleable>
@@ -9307,6 +9312,8 @@
<attr name="pointerIconGrab" format="reference"/>
<!-- Reference to a pointer drawable with STYLE_GRABBING. -->
<attr name="pointerIconGrabbing" format="reference"/>
+ <!-- Reference to a pointer drawable with HANDWRITING. -->
+ <attr name="pointerIconHandwriting" format="reference"/>
</declare-styleable>
<declare-styleable name="PointerIcon">
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index ea6e1f1..a99ba15 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -72,8 +72,8 @@
<item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item>
<item name="secondary_content_alpha_material_light" format="float" type="dimen">0.60</item>
- <item name="highlight_alpha_material_light" format="float" type="dimen">0.10</item>
- <item name="highlight_alpha_material_dark" format="float" type="dimen">0.10</item>
+ <item name="highlight_alpha_material_light" format="float" type="dimen">0.5</item>
+ <item name="highlight_alpha_material_dark" format="float" type="dimen">0.5</item>
<item name="highlight_alpha_material_colored" format="float" type="dimen">0.10</item>
<!-- Primary & accent colors -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0b5d66d..be22095 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2003,6 +2003,9 @@
STREAM_MUSIC as if it's on TV platform. -->
<bool name="config_single_volume">false</bool>
+ <!-- Volume policy -->
+ <bool name="config_volume_down_to_enter_silent">false</bool>
+
<!-- The number of volume steps for the notification stream -->
<integer name="config_audio_notif_vol_steps">7</integer>
@@ -5414,6 +5417,9 @@
<!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->
<bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool>
+ <!-- Whether using display aspect ratio as a default aspect ratio for all letterboxed apps. -->
+ <bool name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled">false</bool>
+
<!-- Whether the specific behaviour for translucent activities letterboxing is enabled.
TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
<bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
@@ -5802,6 +5808,7 @@
- config_roundedCornerDrawableArray (config in SystemUI resource)
- config_roundedCornerTopDrawableArray (config in SystemUI resource)
- config_roundedCornerBottomDrawableArray (config in SystemUI resource)
+ - config_displayUsiVersionArray
Leave this array empty for single display device and the system will load the default main
built-in related configs.
@@ -5942,6 +5949,10 @@
When this resource is empty, that button will not be shown. -->
<string name="config_supervisedUserCreationPackage" translatable="false"></string>
+ <!-- Flag indicating whether the show Stylus pointer icon.
+ If set, a pointer icon will be shown over the location of a stylus pointer.-->
+ <bool name="config_enableStylusPointerIcon">false</bool>
+
<!-- Determines whether SafetyCenter feature is enabled. -->
<bool name="config_enableSafetyCenter">true</bool>
@@ -6160,4 +6171,21 @@
trusted certificate using the SHA-256 digest algorithm. -->
<string-array name="config_healthConnectMigrationKnownSigners">
</string-array>
+
+ <!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
+ (@see https://universalstylus.org/).
+
+ The i-th value in this array corresponds to the supported USI version of the i-th display
+ listed in config_displayUniqueIdArray. On a single-display device, the
+ config_displayUniqueIdArray may be empty, in which case the only value in this array should
+ be the USI version for the main built-in display.
+
+ If the display does not support USI, the version value should be an empty string. If the
+ display supports USI, the version must be in the following format:
+ "<major-version>.<minor-version>"
+
+ For example, "", "1.0", and "2.0" are valid values. -->
+ <string-array name="config_displayUsiVersionArray" translatable="false">
+ <item>""</item>
+ </string-array>
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index f4b49e6..58b8601 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -128,6 +128,7 @@
<public name="isCredential"/>
<public name="searchResultHighlightColor" />
<public name="focusedSearchResultHighlightColor" />
+ <public name="stylusHandwritingSettingsActivity" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 0a7ffca..79964b3 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1419,6 +1419,7 @@
<item name="pointerIconZoomOut">@drawable/pointer_zoom_out_icon</item>
<item name="pointerIconGrab">@drawable/pointer_grab_icon</item>
<item name="pointerIconGrabbing">@drawable/pointer_grabbing_icon</item>
+ <item name="pointerIconHandwriting">@drawable/pointer_handwriting_icon</item>
</style>
<style name="LargePointer">
@@ -1455,6 +1456,7 @@
<item name="pointerIconZoomOut">@drawable/pointer_zoom_out_large_icon</item>
<item name="pointerIconGrab">@drawable/pointer_grab_large_icon</item>
<item name="pointerIconGrabbing">@drawable/pointer_grabbing_large_icon</item>
+ <item name="pointerIconHandwriting">@drawable/pointer_handwriting_icon</item>
</style>
<!-- @hide -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daedde1..2715c94 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -328,6 +328,7 @@
<java-symbol type="bool" name="config_use_strict_phone_number_comparation_for_kazakhstan" />
<java-symbol type="integer" name="config_phonenumber_compare_min_match" />
<java-symbol type="bool" name="config_single_volume" />
+ <java-symbol type="bool" name="config_volume_down_to_enter_silent" />
<java-symbol type="bool" name="config_voice_capable" />
<java-symbol type="bool" name="config_requireCallCapableAccountForHandle" />
<java-symbol type="bool" name="config_user_notification_of_restrictied_mobile_access" />
@@ -4442,6 +4443,7 @@
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
<java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
+ <java-symbol type="bool" name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled" />
<java-symbol type="bool" name="config_isCompatFakeFocusEnabled" />
<java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
@@ -4779,6 +4781,7 @@
<java-symbol type="array" name="config_mainBuiltInDisplayWaterfallCutout" />
<java-symbol type="array" name="config_secondaryBuiltInDisplayWaterfallCutout" />
<java-symbol type="array" name="config_waterfallCutoutArray" />
+ <java-symbol type="array" name="config_displayUsiVersionArray" />
<java-symbol type="fraction" name="global_actions_vertical_padding_percentage" />
<java-symbol type="fraction" name="global_actions_horizontal_padding_percentage" />
@@ -4810,6 +4813,8 @@
<java-symbol type="string" name="config_supervisedUserCreationPackage"/>
+ <java-symbol type="bool" name="config_enableStylusPointerIcon" />
+
<java-symbol type="bool" name="config_enableSafetyCenter" />
<java-symbol type="bool" name="config_safetyProtectionEnabled" />
diff --git a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
index 966d362..9acb99a 100644
--- a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
+++ b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
@@ -92,9 +92,9 @@
}
fun makeIntentSender(sessionId: Int) = PendingIntent.getBroadcast(context, sessionId,
- Intent(INTENT_ACTION),
+ Intent(INTENT_ACTION).setPackage(context.packageName),
PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_MUTABLE_UNAUDITED).intentSender
+ or PendingIntent.FLAG_MUTABLE).intentSender
fun getResult(unit: TimeUnit, timeout: Long) = results.poll(timeout, unit)
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index bcb13d2..4548730 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -248,7 +248,9 @@
@Test
public void allPendingIntents_containsCustomRemoteViews() {
- PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent("test"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent intent = PendingIntent.getActivity(mContext, 0,
+ new Intent("test").setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
RemoteViews contentView = new RemoteViews(mContext.getPackageName(), 0 /* layoutId */);
contentView.setOnClickPendingIntent(1 /* id */, intent);
@@ -1578,7 +1580,8 @@
* Creates a PendingIntent with the given action.
*/
private PendingIntent createPendingIntent(String action) {
- return PendingIntent.getActivity(mContext, 0, new Intent(action),
+ return PendingIntent.getActivity(mContext, 0,
+ new Intent(action).setPackage(mContext.getPackageName()),
PendingIntent.FLAG_MUTABLE);
}
}
diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
index 05775bc..1b52f80 100644
--- a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
+++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
@@ -32,14 +32,16 @@
registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_GRANTED);
addIntermediate("after-register");
PendingIntent is = PendingIntent.getBroadcast(getContext(), 0,
- makeBroadcastIntent(BROADCAST_REGISTERED), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ makeBroadcastIntent(BROADCAST_REGISTERED).setPackage(getContext().getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
is.send();
waitForResultOrThrow(BROADCAST_TIMEOUT);
is.cancel();
}
public void testRegisteredReceivePermissionDenied() throws Exception {
- final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
+ final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED)
+ .setPackage(getContext().getPackageName());
setExpectedReceivers(new String[]{RECEIVER_RESULTS});
registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_DENIED);
@@ -52,7 +54,8 @@
}
};
- PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent,
+ PendingIntent.FLAG_MUTABLE);
is.send(Activity.RESULT_CANCELED, finish, null);
waitForResultOrThrow(BROADCAST_TIMEOUT);
is.cancel();
@@ -61,14 +64,16 @@
public void testLocalReceivePermissionGranted() throws Exception {
setExpectedReceivers(new String[]{RECEIVER_LOCAL});
PendingIntent is = PendingIntent.getBroadcast(getContext(), 0,
- makeBroadcastIntent(BROADCAST_LOCAL_GRANTED), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ makeBroadcastIntent(BROADCAST_LOCAL_GRANTED)
+ .setPackage(getContext().getPackageName()), PendingIntent.FLAG_MUTABLE);
is.send();
waitForResultOrThrow(BROADCAST_TIMEOUT);
is.cancel();
}
public void testLocalReceivePermissionDenied() throws Exception {
- final Intent intent = makeBroadcastIntent(BROADCAST_LOCAL_DENIED);
+ final Intent intent = makeBroadcastIntent(BROADCAST_LOCAL_DENIED)
+ .setPackage(getContext().getPackageName());
setExpectedReceivers(new String[]{RECEIVER_RESULTS});
@@ -79,7 +84,8 @@
}
};
- PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent,
+ PendingIntent.FLAG_MUTABLE);
is.send(Activity.RESULT_CANCELED, finish, null);
waitForResultOrThrow(BROADCAST_TIMEOUT);
is.cancel();
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
index 6186192..e0eb197 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
@@ -48,7 +48,8 @@
public void setUp() {
final Context context = InstrumentationRegistry.getContext();
mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
- new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
+ new Intent().setPackage(context.getPackageName()),
+ PendingIntent.FLAG_MUTABLE /* flags */);
mIcon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
}
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
new file mode 100644
index 0000000..7706d9a
--- /dev/null
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -0,0 +1,796 @@
+/*
+ * 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 android.text.method;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ReplacementSpan;
+import android.view.View;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InsertModeTransformationMethodTest {
+ private static View sView;
+ private static final String TEXT = "abc def";
+
+ @BeforeClass
+ public static void setupClass() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ sView = new View(context);
+ }
+
+ @Test
+ public void transformedText_charAt() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, false, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final CharSequence expected =
+ TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+ assertCharSequence(transformedText, expected);
+ }
+ }
+
+ @Test
+ public void transformedText_charAt_singleLine() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, true, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final CharSequence expected =
+ TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+ assertCharSequence(transformedText, expected);
+ }
+ }
+
+ @Test
+ public void transformedText_charAt_editing() {
+ transformedText_charAt_editing(false, "\n\n");
+ }
+
+ @Test
+ public void transformedText_charAt_singleLine_editing() {
+ transformedText_charAt_editing(true, "\uFFFD");
+ }
+
+ public void transformedText_charAt_editing(boolean singleLine, String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final CharSequence transformedText = transformationMethod.getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+ // original text is "abcxx def" after insertion.
+ text.insert(3, "xx");
+ assertCharSequence(transformedText, "abcxx" + placeholder + " def");
+
+ // original text is "abcxx vvdef" after insertion.
+ text.insert(6, "vv");
+ assertCharSequence(transformedText, "abcxx" + placeholder + " vvdef");
+
+ // original text is "abc vvdef" after deletion.
+ text.delete(3, 5);
+ assertCharSequence(transformedText, "abc" + placeholder + " vvdef");
+
+ // original text is "abc def" after deletion.
+ text.delete(4, 6);
+ assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+ // original text is "abdef" after deletion.
+ // the placeholder is now inserted at index 2, since the deletion covers the index 3.
+ text.delete(2, 4);
+ assertCharSequence(transformedText, "ab" + placeholder + "def");
+
+ // original text is "axxdef" after replace.
+ text.replace(1, 2, "xx");
+ assertCharSequence(transformedText, "axx" + placeholder + "def");
+
+ // original text is "axxvvf" after replace.
+ text.replace(3, 5, "vv");
+ assertCharSequence(transformedText, "axx" + placeholder + "vvf");
+
+ // original text is "abc def" after replace.
+ // the placeholder is inserted at index 6 after the insertion, since the replacement covers
+ // the index 3.
+ text.replace(1, 5, "bc de");
+ assertCharSequence(transformedText, "abc de" + placeholder + "f");
+ }
+
+ @Test
+ public void transformedText_subSequence() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, false, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final CharSequence expected =
+ TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+ for (int start = 0; start < transformedText.length(); ++start) {
+ for (int end = start; end <= transformedText.length(); ++end) {
+ assertCharSequence(transformedText.subSequence(start, end),
+ expected.subSequence(start, end));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void transformedText_subSequence_singleLine() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, true, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final CharSequence expected =
+ TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+ for (int start = 0; start < transformedText.length(); ++start) {
+ for (int end = start; end <= transformedText.length(); ++end) {
+ assertCharSequence(transformedText.subSequence(start, end),
+ expected.subSequence(start, end));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void transformedText_toString() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, false, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final String expected =
+ TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+ assertThat(transformedText.toString()).isEqualTo(expected);
+ }
+ }
+
+ @Test
+ public void transformedText_toString_singleLine() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, true, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final String expected =
+ TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+ assertThat(transformedText.toString()).isEqualTo(expected);
+ }
+ }
+
+
+ @Test
+ public void transformedText_getSpans() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText "abc\n\n def", the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [2, 6)
+ // span3: [6, 7)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, false, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ // only span1 is in the range of [0, 2).
+ final TestSpan[] spans0to2 = transformedText.getSpans(0, 2, TestSpan.class);
+ assertThat(spans0to2.length).isEqualTo(1);
+ assertThat(spans0to2[0]).isEqualTo(span1);
+
+ // span1 and span2 are in the range of [1, 6).
+ final TestSpan[] spans1to6 = transformedText.getSpans(1, 6, TestSpan.class);
+ assertThat(spans1to6.length).isEqualTo(2);
+ assertThat(spans1to6[0]).isEqualTo(span1);
+ assertThat(spans1to6[1]).isEqualTo(span2);
+
+ // only span2 is in the range of [4, 6).
+ final TestSpan[] spans4to6 = transformedText.getSpans(4, 6, TestSpan.class);
+ assertThat(spans4to6.length).isEqualTo(1);
+ assertThat(spans4to6[0]).isEqualTo(span2);
+
+ // span2 and span3 are in the range of [4, 7).
+ final TestSpan[] spans4to7 = transformedText.getSpans(4, 7, TestSpan.class);
+ assertThat(spans4to7.length).isEqualTo(2);
+ assertThat(spans4to7[0]).isEqualTo(span2);
+ assertThat(spans4to7[1]).isEqualTo(span3);
+
+ // only span3 is in the range of [6, 7).
+ final TestSpan[] spans6to7 = transformedText.getSpans(6, 7, TestSpan.class);
+ assertThat(spans6to7.length).isEqualTo(1);
+ assertThat(spans6to7[0]).isEqualTo(span3);
+
+ // there is no span in the range of [7, 9).
+ final TestSpan[] spans7to9 = transformedText.getSpans(7, 9, TestSpan.class);
+ assertThat(spans7to9.length).isEqualTo(0);
+ }
+
+ @Test
+ public void transformedText_getSpans_singleLine() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [2, 5)
+ // span3: [5, 6)
+ // There should also be a ReplacementSpan in the range [3, 4).
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, true, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ // only span1 is in the range of [0, 2).
+ final TestSpan[] spans0to2 = transformedText.getSpans(0, 2, TestSpan.class);
+ assertThat(spans0to2.length).isEqualTo(1);
+ assertThat(spans0to2[0]).isEqualTo(span1);
+
+ // span1 and span2 are in the range of [1, 5).
+ final TestSpan[] spans1to4 = transformedText.getSpans(1, 4, TestSpan.class);
+ assertThat(spans1to4.length).isEqualTo(2);
+ assertThat(spans1to4[0]).isEqualTo(span1);
+ assertThat(spans1to4[1]).isEqualTo(span2);
+
+ // only span2 is in the range of [3, 5).
+ final TestSpan[] spans3to5 = transformedText.getSpans(3, 5, TestSpan.class);
+ assertThat(spans3to5.length).isEqualTo(1);
+ assertThat(spans3to5[0]).isEqualTo(span2);
+
+ // span2 and span3 are in the range of [3, 6).
+ final TestSpan[] spans3to6 = transformedText.getSpans(3, 6, TestSpan.class);
+ assertThat(spans3to6.length).isEqualTo(2);
+ assertThat(spans3to6[0]).isEqualTo(span2);
+ assertThat(spans3to6[1]).isEqualTo(span3);
+
+ // only span3 is in the range of [5, 6).
+ final TestSpan[] spans5to6 = transformedText.getSpans(5, 6, TestSpan.class);
+ assertThat(spans5to6.length).isEqualTo(1);
+ assertThat(spans5to6[0]).isEqualTo(span3);
+
+ // there is no span in the range of [6, 8)
+ final TestSpan[] spans6to8 = transformedText.getSpans(6, 8, TestSpan.class);
+ assertThat(spans6to8.length).isEqualTo(0);
+
+ // When it's singleLine, there should be a ReplacementSpan in the range [3, 4)
+ final ReplacementSpan[] replacementSpans3to4 =
+ transformedText.getSpans(3, 4, ReplacementSpan.class);
+ assertThat(replacementSpans3to4.length).isEqualTo(1);
+
+ final ReplacementSpan[] replacementSpans0to3 =
+ transformedText.getSpans(0, 3, ReplacementSpan.class);
+ assertThat(replacementSpans0to3.length).isEqualTo(0);
+
+ final ReplacementSpan[] replacementSpans4to8 =
+ transformedText.getSpans(4, 8, ReplacementSpan.class);
+ assertThat(replacementSpans4to8.length).isEqualTo(0);
+ }
+
+ @Test
+ public void transformedText_getSpanStartAndEnd() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [2, 6)
+ // span3: [6, 7)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, false, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ assertThat(transformedText.getSpanStart(span1)).isEqualTo(0);
+ assertThat(transformedText.getSpanEnd(span1)).isEqualTo(3);
+
+ assertThat(transformedText.getSpanStart(span2)).isEqualTo(2);
+ assertThat(transformedText.getSpanEnd(span2)).isEqualTo(6);
+
+ assertThat(transformedText.getSpanStart(span3)).isEqualTo(6);
+ assertThat(transformedText.getSpanEnd(span3)).isEqualTo(7);
+ }
+
+ @Test
+ public void transformedText_getSpanStartAndEnd_singleLine() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [2, 5)
+ // span3: [5, 6)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, true, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ assertThat(transformedText.getSpanStart(span1)).isEqualTo(0);
+ assertThat(transformedText.getSpanEnd(span1)).isEqualTo(3);
+
+ assertThat(transformedText.getSpanStart(span2)).isEqualTo(2);
+ assertThat(transformedText.getSpanEnd(span2)).isEqualTo(5);
+
+ assertThat(transformedText.getSpanStart(span3)).isEqualTo(5);
+ assertThat(transformedText.getSpanEnd(span3)).isEqualTo(6);
+
+ final ReplacementSpan[] replacementSpans =
+ transformedText.getSpans(0, 8, ReplacementSpan.class);
+ assertThat(transformedText.getSpanStart(replacementSpans[0])).isEqualTo(3);
+ assertThat(transformedText.getSpanEnd(replacementSpans[0])).isEqualTo(4);
+ }
+
+ @Test
+ public void transformedText_getSpanFlag() {
+ transformedText_getSpanFlag(false);
+ }
+
+ @Test
+ public void transformedText_getSpanFlag_singleLine() {
+ transformedText_getSpanFlag(true);
+ }
+
+ public void transformedText_getSpanFlag(boolean singleLine) {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ assertThat(transformedText.getSpanFlags(span1)).isEqualTo(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(transformedText.getSpanFlags(span2)).isEqualTo(Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertThat(transformedText.getSpanFlags(span3)).isEqualTo(Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+
+ @Test
+ public void transformedText_nextSpanTransition() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 1, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [1, 6)
+ // span3: [6, 7)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, false, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+ final int[] expectedTransitions = new int[] { 0, 1, 3, 6, 7 };
+ assertNextSpanTransition(transformedText, expectedTransitions, TestSpan.class);
+ }
+
+ @Test
+ public void transformedText_nextSpanTransition_singleLine() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 1, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [1, 5)
+ // span3: [5, 6)
+ // there is also a ReplacementSpan at range [3, 4)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, true, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+ final int[] expectedTransitions = new int[] { 0, 1, 3, 4, 5, 6 };
+ assertNextSpanTransition(transformedText, expectedTransitions, Object.class);
+ }
+
+ @Test
+ public void transformedText_originalToTransformed() {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(2, false, null);
+ final OffsetMapping transformedText =
+ (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+ // "abc def" is transformed to "ab\n\nc def".
+ final int[] mappedCharacterOffsets = new int[] { 0, 1, 4, 5, 6, 7, 8 };
+ assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCharacterOffsets);
+
+ // "abc def" is transformed to "ab\n\nc def".
+ // the cursor before 'c' is mapped to index position before "\n\n".
+ final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 5, 6, 7, 8 };
+ assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CURSOR,
+ mappedCursorOffsets);
+ }
+
+ @Test
+ public void transformedText_originalToTransformed_singleLine() {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(2, true, null);
+ final OffsetMapping transformedText =
+ (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+ // "abc def" is transformed to "ab\uFFFDc def".
+ final int[] mappedCharacterOffsets = new int[] { 0, 1, 3, 4, 5, 6, 7 };
+ assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCharacterOffsets);
+
+ // "abc def" is transformed to "ab\uFFFDc def".
+ // the cursor before 'c' is mapped to index position before "\uFFFD".
+ final int[] mappedCursorOffsets = new int[] { 0, 1, 3, 4, 5, 6, 7 };
+ assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCursorOffsets);
+ }
+
+ @Test
+ public void transformedText_transformedToOriginal() {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(2, false, null);
+ final OffsetMapping transformedText =
+ (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+ // "abc def" is transformed to "ab\n\nc def".
+ // the two '\n' characters have no corresponding character; map them to 'c'.
+ final int[] mappedCharacterOffsets = new int[] { 0, 1, 2, 2, 2, 3, 4, 5, 6 };
+ assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCharacterOffsets);
+
+ // offset 2 and 3 (cursor positions before the two '\n' characters) are mapped to index 2
+ // (cursor position before 'c' in the original text)
+ final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 2, 2, 3, 4, 5, 6 };
+ assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CURSOR,
+ mappedCursorOffsets);
+ }
+
+ @Test
+ public void transformedText_transformedToOriginal_singleLine() {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(2, true, null);
+ final OffsetMapping transformedText =
+ (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+ // "abc def" is transformed to "ab\uFFFDc def".
+ // '\uFFFD' has no corresponding character; map it to 'c'.
+ final int[] mappedCharacterOffsets = new int[] { 0, 1, 2, 2, 3, 4, 5, 6 };
+ assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCharacterOffsets);
+
+ // offset 2 (cursor positions before '\uFFFD') is mapped to index 2 (cursor position before
+ // 'c' in the original text)
+ final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 2, 3, 4, 5, 6 };
+ assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCursorOffsets);
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_insertion() {
+ transformedText_getHighlightStartAndEnd_insertion(false, "\n\n");
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_insertion_singleLine() {
+ transformedText_getHighlightStartAndEnd_insertion(true, "\uFDDD");
+ }
+
+ public void transformedText_getHighlightStartAndEnd_insertion(boolean singleLine,
+ String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final InsertModeTransformationMethod.TransformedText transformedText =
+ (InsertModeTransformationMethod.TransformedText) transformationMethod
+ .getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // note: the placeholder text is also highlighted.
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "abcxx def" after insertion.
+ // the placeholder is now inserted at index 5.
+ // the highlight start is still 3.
+ // the highlight end now is 5 + placeholder.length(), including the newly inserted text.
+ text.insert(3, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+ // original text is "abcxxvv def" after insertion.
+ // the placeholder is now inserted at index 7.
+ // the highlight start is still 3.
+ // the highlight end now is 7 + placeholder.length(), including the newly inserted text.
+ text.insert(5, "vv");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "abzzcxxvv def" after insertion.
+ // the placeholder is now inserted at index 9.
+ // the highlight start is 5, since the insertion happens before the highlight range.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(2, "zz");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(5);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ // original text is "abzzcxxvv iidef" after insertion.
+ // the placeholder is still inserted at index 9.
+ // the highlight start is still 5, since the insertion happens after the highlight range.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(10, "ii");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(5);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_deletion() {
+ transformedText_getHighlightStartAndEnd_deletion(false, "\n\n");
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_insertion_deletion() {
+ transformedText_getHighlightStartAndEnd_deletion(true, "\uFDDD");
+ }
+
+ public void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
+ String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final InsertModeTransformationMethod.TransformedText transformedText =
+ (InsertModeTransformationMethod.TransformedText) transformationMethod
+ .getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // note: the placeholder text is also highlighted.
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "abcxxxxxx def" after insertion.
+ // the placeholder is now inserted at index 9.
+ // the highlight start is still 3.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(3, "xxxxxx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ // original text is "abxxxxxx def" after deletion.
+ // the placeholder is now inserted at index 6.
+ // the highlight start is 2, since the deletion happens before the highlight range.
+ // the highlight end now is 8 + placeholder.length().
+ text.delete(2, 3);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(8 + placeholder.length());
+
+ // original text is "abxxx def" after deletion.
+ // the placeholder is now inserted at index 5.
+ // the highlight start is still 2, since the deletion happens in the highlight range.
+ // the highlight end now is 5 + placeholder.length().
+ text.delete(2, 5);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+ // original text is "abxxx d" after deletion.
+ // the placeholder is now inserted at index 5.
+ // the highlight start is still 2, since the deletion happens after the highlight range.
+ // the highlight end now is still 5 + placeholder.length().
+ text.delete(7, 9);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+ // original text is "af" after deletion.
+ // the placeholder is now inserted at index 1.
+ // the highlight start is 1, since the deletion covers highlight range.
+ // the highlight end is 1 + placeholder.length().
+ text.delete(1, 5);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(1);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(1 + placeholder.length());
+ }
+
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_replace() {
+ transformedText_getHighlightStartAndEnd_replace(false, "\n\n");
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_insertion__replace() {
+ transformedText_getHighlightStartAndEnd_replace(true, "\uFDDD");
+ }
+
+ public void transformedText_getHighlightStartAndEnd_replace(boolean singleLine,
+ String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final InsertModeTransformationMethod.TransformedText transformedText =
+ (InsertModeTransformationMethod.TransformedText) transformationMethod
+ .getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // note: the placeholder text is also highlighted.
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "abcxxxxxx def" after insertion.
+ // the placeholder is now inserted at index 9.
+ // the highlight start is still 3.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(3, "xxxxxx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ // original text is "abvvxxxxxx def" after replace.
+ // the replacement happens before the highlight range; highlight range is offset by 1
+ // the placeholder is now inserted at index 10,
+ // the highlight start is 4.
+ // the highlight end is 10 + placeholder.length().
+ text.replace(2, 3, "vv");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(10 + placeholder.length());
+
+ // original text is "abvvxxx def" after replace.
+ // the replacement happens in the highlight range; highlight end is offset by -3
+ // the placeholder is now inserted at index 7,
+ // the highlight start is still 4.
+ // the highlight end is 7 + placeholder.length().
+ text.replace(5, 9, "x");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "abvvxxxvvv" after replace.
+ // the replacement happens after the highlight range; highlight is not changed
+ // the placeholder is now inserted at index 7,
+ // the highlight start is still 4.
+ // the highlight end is 7 + placeholder.length().
+ text.replace(7, 11, "vvv");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "abxxxxvvv" after replace.
+ // the replacement happens covers the highlight start; highlight start extends to the
+ // replacement start; highlight end is offset by -1
+ // the placeholder is now inserted at index 6,
+ // the highlight start is 2.
+ // the highlight end is 6 + placeholder.length().
+ text.replace(2, 5, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+ // original text is "abxxxxxvv" after replace.
+ // the replacement happens covers the highlight end; highlight end extends to the
+ // replacement end; highlight start stays the same
+ // the placeholder is now inserted at index 7,
+ // the highlight start is 2.
+ // the highlight end is 7 + placeholder.length().
+ text.replace(5, 7, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "axxv" after replace.
+ // the replacement happens covers the highlight range; highlight start is set to the
+ // replacement start; highlight end is set to the replacement end
+ // the placeholder is now inserted at index 3,
+ // the highlight start is 1.
+ // the highlight end is 3 + placeholder.length().
+ text.replace(1, 8, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(1);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+ }
+
+ private static <T> void assertNextSpanTransition(Spanned spanned, int[] transitions,
+ Class<T> type) {
+ int currentTransition = 0;
+ for (int transition : transitions) {
+ assertThat(currentTransition).isEqualTo(transition);
+ currentTransition =
+ spanned.nextSpanTransition(currentTransition, spanned.length(), type);
+ }
+
+ // Make sure there is no transition after the currentTransition.
+ assertThat(currentTransition).isEqualTo(spanned.length());
+ }
+
+ private static void assertCharSequence(CharSequence actual, CharSequence expected) {
+ assertThat(actual.length()).isEqualTo(expected.length());
+ for (int index = 0; index < actual.length(); ++index) {
+ assertThat(actual.charAt(index)).isEqualTo(expected.charAt(index));
+ }
+ }
+
+ private static void assertOriginalToTransformed(OffsetMapping transformedText, int strategy,
+ int[] expected) {
+ for (int offset = 0; offset < expected.length; ++offset) {
+ assertThat(transformedText.originalToTransformed(offset, strategy))
+ .isEqualTo(expected[offset]);
+ }
+ }
+
+ private static void assertTransformedToOriginal(OffsetMapping transformedText, int strategy,
+ int[] expected) {
+ for (int offset = 0; offset < expected.length; ++offset) {
+ assertThat(transformedText.transformedToOriginal(offset, strategy))
+ .isEqualTo(expected[offset]);
+ }
+ }
+
+ private static class TestSpan { }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
index e7d7d640..7bef55e 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -62,6 +62,8 @@
assertThat(clone.supportsSwitchingToNextInputMethod(), is(false));
assertThat(imi.isInlineSuggestionsEnabled(), is(false));
assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
+ assertThat(imi.supportsStylusHandwriting(), is(false));
+ assertThat(imi.createStylusHandwritingSettingsActivityIntent(), equalTo(null));
}
@Test
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
index 79aeaa3..90f7d06 100644
--- a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.graphics.PointF;
import android.graphics.RectF;
+import android.os.CancellationSignal;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -86,6 +87,14 @@
}
@Test
+ public void testInsertModeGesture() {
+ verifyEqualityAfterUnparcel(new InsertModeGesture.Builder()
+ .setInsertionPoint(new PointF(1, 1)).setFallbackText("")
+ .setCancellationSignal(new CancellationSignal())
+ .build());
+ }
+
+ @Test
public void testDeleteGestureGesture() {
verifyEqualityAfterUnparcel(new DeleteGesture.Builder()
.setGranularity(HandwritingGesture.GRANULARITY_WORD)
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index bbf9f3c..31c5a76 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -324,7 +324,9 @@
RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
nested.setOnClickPendingIntent(
R.id.text,
- PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE)
);
RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host);
@@ -341,7 +343,9 @@
RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
inner.setOnClickPendingIntent(
R.id.text,
- PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE)
);
RemoteViews listItem = new RemoteViews(inner, inner);
@@ -357,7 +361,9 @@
RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
inner.setOnClickPendingIntent(
R.id.text,
- PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE)
);
RemoteViews listItem = new RemoteViews(
@@ -559,7 +565,9 @@
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
for (int i = 1; i < 10; i++) {
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
- new Intent("android.widget.RemoteViewsTest_" + i), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ new Intent("android.widget.RemoteViewsTest_" + i)
+ .setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
views.setOnClickPendingIntent(i, pi);
}
try {
@@ -575,7 +583,8 @@
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
- new Intent("test"), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ new Intent("test").setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
views.setOnClickPendingIntent(1, pi);
RemoteViews withCookie = parcelAndRecreateWithPendingIntentCookie(views, whitelistToken);
@@ -606,8 +615,9 @@
public void sharedElement_pendingIntent_notifyParent() throws Exception {
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
- new Intent("android.widget.RemoteViewsTest_shared_element"),
- PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ new Intent("android.widget.RemoteViewsTest_shared_element")
+ .setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
views.setOnClickResponse(R.id.image, RemoteViews.RemoteResponse.fromPendingIntent(pi)
.addSharedElement(0, "e0")
.addSharedElement(1, "e1")
diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
index 3e640c1..bdf42ac 100644
--- a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
@@ -18,6 +18,7 @@
import static junit.framework.Assert.assertEquals;
+import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -27,6 +28,10 @@
import androidx.test.InstrumentationRegistry;
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
+
import org.junit.Test;
import java.util.List;
@@ -96,7 +101,8 @@
Intent intent = new Intent();
AbstractResolverComparator testComparator =
- new AbstractResolverComparator(context, intent) {
+ new AbstractResolverComparator(context, intent,
+ Lists.newArrayList(context.getUser())) {
@Override
int compare(ResolveInfo lhs, ResolveInfo rhs) {
@@ -109,7 +115,7 @@
void doCompute(List<ResolverActivity.ResolvedComponentInfo> targets) {}
@Override
- float getScore(ComponentName name) {
+ float getScore(TargetInfo targetInfo) {
return 0;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 78a75db..c06df94 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -545,13 +545,17 @@
return true;
};
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ DisplayResolveInfo testDri =
+ activity.createTestDisplayResolveInfo(sendIntent, toChoose, "testLabel", "testInfo",
+ sendIntent,/* resolveInfoPresentationGetter */ null);
onView(withText(toChoose.activityInfo.name))
.perform(click());
waitForIdle();
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .updateChooserCounts(Mockito.anyString(), anyInt(), Mockito.anyString());
+ .updateChooserCounts(Mockito.anyString(), any(UserHandle.class),
+ Mockito.anyString());
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .updateModel(toChoose.activityInfo.getComponentName());
+ .updateModel(testDri);
assertThat(activity.getIsSelected(), is(true));
}
diff --git a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
index fbbe57c..573135ffa 100644
--- a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
+++ b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
@@ -19,6 +19,8 @@
import android.content.ComponentName;
import android.content.pm.ResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -45,14 +47,15 @@
}
@Override
- public float getScore(ComponentName name) {
+ public float getScore(TargetInfo targetInfo) {
return 0.0f; // Models are not required to provide numerical scores.
}
@Override
- public void notifyOnTargetSelected(ComponentName componentName) {
+ public void notifyOnTargetSelected(TargetInfo targetInfo) {
System.out.println(
- "User selected " + componentName + " under model " + System.identityHashCode(this));
+ "User selected " + targetInfo.getResolvedComponentName() + " under model "
+ + System.identityHashCode(this));
}
private FakeResolverComparatorModel(Comparator<ResolveInfo> comparator) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index b0f1e04..8f6cee3 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -86,6 +86,7 @@
config.locale = Locale.getDefault();
List<ResolveInfo> services = new ArrayList<>();
mUsm = new UsageStatsManager(mMockContext, mMockService);
+ when(mMockContext.createContextAsUser(any(), anyInt())).thenReturn(mMockContext);
when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
when(mMockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(services);
when(mMockResources.getConfiguration()).thenReturn(config);
@@ -126,7 +127,7 @@
UserHandle.SYSTEM);
mController.sort(new ArrayList<ResolvedComponentInfo>());
long beforeReport = getCount(mUsm, packageName, action, annotation);
- mController.updateChooserCounts(packageName, UserHandle.USER_CURRENT, action);
+ mController.updateChooserCounts(packageName, UserHandle.SYSTEM, action);
long afterReport = getCount(mUsm, packageName, action, annotation);
assertThat(afterReport, is(beforeReport + 1l));
}
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
new file mode 100644
index 0000000..9b9a84b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.internal.app.procstats;
+
+import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import junit.framework.TestCase;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
+
+/** Provides test cases for ProcessStats. */
+public class ProcessStatsTest extends TestCase {
+
+ private static final String APP_1_PACKAGE_NAME = "com.android.testapp";
+ private static final int APP_1_UID = 5001;
+ private static final long APP_1_VERSION = 10;
+ private static final String APP_1_PROCESS_NAME = "com.android.testapp.p";
+ private static final String APP_1_SERVICE_NAME = "com.android.testapp.service";
+
+ private static final String APP_2_PACKAGE_NAME = "com.android.testapp2";
+ private static final int APP_2_UID = 5002;
+ private static final long APP_2_VERSION = 30;
+ private static final String APP_2_PROCESS_NAME = "com.android.testapp2.p";
+
+ private static final long NOW_MS = 123000;
+ private static final int DURATION_SECS = 6;
+
+ @Mock StatsEventOutput mStatsEventOutput;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ }
+
+ @SmallTest
+ public void testDumpProcessState() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processStats.getProcessStateLocked(
+ APP_2_PACKAGE_NAME, APP_2_UID, APP_2_VERSION, APP_2_PROCESS_NAME);
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_2_UID),
+ eq(APP_2_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testNonZeroProcessStateDuration() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ ProcessState processState =
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processState.setCombinedState(STATE_TOP, NOW_MS);
+ processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(DURATION_SECS),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testDumpProcessAssociation() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ AssociationState associationState =
+ processStats.getAssociationStateLocked(
+ APP_1_PACKAGE_NAME,
+ APP_1_UID,
+ APP_1_VERSION,
+ APP_1_PROCESS_NAME,
+ APP_1_SERVICE_NAME);
+ AssociationState.SourceState sourceState =
+ associationState.startSource(APP_2_UID, APP_2_PROCESS_NAME, APP_2_PACKAGE_NAME);
+ sourceState.stop();
+ processStats.dumpProcessAssociation(
+ FrameworkStatsLog.PROCESS_ASSOCIATION, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_ASSOCIATION),
+ eq(APP_2_UID),
+ eq(APP_2_PROCESS_NAME),
+ eq(APP_1_UID),
+ eq(APP_1_SERVICE_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(APP_1_PROCESS_NAME));
+ }
+}
diff --git a/core/tests/notificationtests/src/android/app/NotificationStressTest.java b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
index e5000a4..b2914d8 100644
--- a/core/tests/notificationtests/src/android/app/NotificationStressTest.java
+++ b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
@@ -110,8 +110,9 @@
private void sendNotification(int id, CharSequence text) {
// Fill in arbitrary content
- Intent intent = new Intent(Intent.ACTION_VIEW);
- PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+ PendingIntent.FLAG_MUTABLE);
CharSequence title = text + " " + id;
CharSequence subtitle = String.valueOf(System.currentTimeMillis());
// Create "typical" notification with random icon
diff --git a/core/tests/screenshothelpertests/Android.bp b/core/tests/screenshothelpertests/Android.bp
index 37af99c..3c71e6e 100644
--- a/core/tests/screenshothelpertests/Android.bp
+++ b/core/tests/screenshothelpertests/Android.bp
@@ -13,7 +13,7 @@
srcs: [
"src/**/*.java",
],
-
+
static_libs: [
"frameworks-base-testutils",
"androidx.test.runner",
@@ -21,6 +21,7 @@
"androidx.test.ext.junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
+ "testng",
],
libs: [
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 2719431..5c9894e 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -17,6 +17,7 @@
package com.android.internal.util;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
@@ -31,9 +32,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.os.Bundle;
+import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
@@ -79,30 +82,48 @@
@Test
public void testFullscreenScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ mScreenshotHelper.takeScreenshot(
WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
+ public void testFullscreenScreenshotRequest() {
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .build();
+ mScreenshotHelper.takeScreenshot(request, mHandler, null);
+ }
+
+ @Test
public void testProvidedImageScreenshot() {
- mScreenshotHelper.provideScreenshot(
- new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
+ HardwareBuffer buffer = HardwareBuffer.create(
+ 10, 10, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_PROVIDED_IMAGE, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .setTopComponent(new ComponentName("", ""))
+ .setTaskId(1)
+ .setUserId(1)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(new Rect())
+ .setInsets(Insets.NONE)
+ .build();
+ mScreenshotHelper.takeScreenshot(request, mHandler, null);
}
@Test
public void testScreenshotTimesOut() {
long timeoutMs = 10;
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .build();
CountDownLatch lock = new CountDownLatch(1);
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
- mHandler,
- timeoutMs,
+ mScreenshotHelper.takeScreenshotInternal(request, mHandler,
uri -> {
assertNull(uri);
lock.countDown();
- });
+ }, timeoutMs);
try {
// Add tolerance for delay to prevent flakes.
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
new file mode 100644
index 0000000..30540a5
--- /dev/null
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ScreenshotRequestTest {
+ private final ComponentName mComponentName =
+ new ComponentName("android.test", "android.test.Component");
+
+ @Test
+ public void testSimpleScreenshot() {
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertNull("Top component was expected to be null", out.getTopComponent());
+ assertEquals(INVALID_TASK_ID, out.getTaskId());
+ assertEquals(USER_NULL, out.getUserId());
+ assertNull("Bitmap was expected to be null", out.getBitmap());
+ assertNull("Bounds were expected to be null", out.getBoundsInScreen());
+ assertEquals(Insets.NONE, out.getInsets());
+ }
+
+ @Test
+ public void testProvidedScreenshot() {
+ Bitmap bitmap = makeHardwareBitmap(50, 50);
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(mComponentName)
+ .setTaskId(2)
+ .setUserId(3)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+ .setInsets(Insets.of(2, 3, 4, 5))
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_PROVIDED_IMAGE, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertEquals(mComponentName, out.getTopComponent());
+ assertEquals(2, out.getTaskId());
+ assertEquals(3, out.getUserId());
+ assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+ assertEquals(new Rect(10, 10, 60, 60), out.getBoundsInScreen());
+ assertEquals(Insets.of(2, 3, 4, 5), out.getInsets());
+ }
+
+ @Test
+ public void testProvidedScreenshot_nullBitmap() {
+ ScreenshotRequest.Builder inBuilder =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(mComponentName)
+ .setTaskId(2)
+ .setUserId(3)
+ .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+ .setInsets(Insets.of(2, 3, 4, 5));
+
+ assertThrows(IllegalStateException.class, inBuilder::build);
+ }
+
+ @Test
+ public void testFullScreenshot_withBitmap() {
+ // A bitmap added to a FULLSCREEN request will be ignored, but it's technically valid
+ Bitmap bitmap = makeHardwareBitmap(50, 50);
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+ .setBitmap(bitmap)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertNull(out.getTopComponent());
+ assertEquals(INVALID_TASK_ID, out.getTaskId());
+ assertEquals(USER_NULL, out.getUserId());
+ assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+ assertNull("Bounds expected to be null", out.getBoundsInScreen());
+ assertEquals(Insets.NONE, out.getInsets());
+ }
+
+ private Bitmap makeHardwareBitmap(int width, int height) {
+ HardwareBuffer buffer = HardwareBuffer.create(
+ width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bc3af1d..8b7265e 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2599,6 +2599,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "273212558": {
+ "message": " info=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"274773837": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL transit=%s Callers=%s",
"level": "VERBOSE",
@@ -3937,6 +3943,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1621562070": {
+ "message": " startWCT=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"1628345525": {
"message": "Now opening app %s",
"level": "VERBOSE",
@@ -4345,6 +4357,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
+ "2021079047": {
+ "message": "%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"2022422429": {
"message": "createAnimationAdapter(): container=%s",
"level": "DEBUG",
@@ -4551,6 +4569,9 @@
"WM_DEBUG_WINDOW_TRANSITIONS": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WINDOW_TRANSITIONS_MIN": {
+ "tag": "WindowManager"
+ },
"WM_ERROR": {
"tag": "WindowManager"
},
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 89d6304..f815a5e 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -992,6 +992,15 @@
}
/**
+ * Notifies the hardware renderer about upcoming expensive frames.
+ *
+ * @hide
+ */
+ public void notifyExpensiveFrame() {
+ nNotifyExpensiveFrame(mNativeProxy);
+ }
+
+ /**
* b/68769804, b/66945974: For low FPS experiments.
*
* @hide
@@ -1553,4 +1562,6 @@
private static native void nSetRtAnimationsEnabled(boolean rtAnimationsEnabled);
private static native void nNotifyCallbackPending(long nativeProxy);
+
+ private static native void nNotifyExpensiveFrame(long nativeProxy);
}
diff --git a/graphics/java/android/graphics/drawable/LottieDrawable.java b/graphics/java/android/graphics/drawable/LottieDrawable.java
deleted file mode 100644
index c1f1b50..0000000
--- a/graphics/java/android/graphics/drawable/LottieDrawable.java
+++ /dev/null
@@ -1,151 +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 android.graphics.drawable;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
-
-import dalvik.annotation.optimization.FastNative;
-
-import libcore.util.NativeAllocationRegistry;
-
-import java.io.IOException;
-
-/**
- * {@link Drawable} for drawing Lottie files.
- *
- * <p>The framework handles decoding subsequent frames in another thread and
- * updating when necessary. The drawable will only animate while it is being
- * displayed.</p>
- *
- * @hide
- */
-@SuppressLint("NotCloseable")
-public class LottieDrawable extends Drawable implements Animatable {
- private long mNativePtr;
-
- /**
- * Create an animation from the provided JSON string
- * @hide
- */
- private LottieDrawable(@NonNull String animationJson) throws IOException {
- mNativePtr = nCreate(animationJson);
- if (mNativePtr == 0) {
- throw new IOException("could not make LottieDrawable from json");
- }
-
- final long nativeSize = nNativeByteSize(mNativePtr);
- NativeAllocationRegistry registry = new NativeAllocationRegistry(
- LottieDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
- registry.registerNativeAllocation(this, mNativePtr);
- }
-
- /**
- * Factory for LottieDrawable, throws IOException if native Skottie Builder fails to parse
- */
- public static LottieDrawable makeLottieDrawable(@NonNull String animationJson)
- throws IOException {
- return new LottieDrawable(animationJson);
- }
-
-
-
- /**
- * Draw the current frame to the Canvas.
- * @hide
- */
- @Override
- public void draw(@NonNull Canvas canvas) {
- if (mNativePtr == 0) {
- throw new IllegalStateException("called draw on empty LottieDrawable");
- }
-
- nDraw(mNativePtr, canvas.getNativeCanvasWrapper());
- }
-
- /**
- * Start the animation. Needs to be called before draw calls.
- * @hide
- */
- @Override
- public void start() {
- if (mNativePtr == 0) {
- throw new IllegalStateException("called start on empty LottieDrawable");
- }
-
- if (nStart(mNativePtr)) {
- invalidateSelf();
- }
- }
-
- /**
- * Stops the animation playback. Does not release anything.
- * @hide
- */
- @Override
- public void stop() {
- if (mNativePtr == 0) {
- throw new IllegalStateException("called stop on empty LottieDrawable");
- }
- nStop(mNativePtr);
- }
-
- /**
- * Return whether the animation is currently running.
- */
- @Override
- public boolean isRunning() {
- if (mNativePtr == 0) {
- throw new IllegalStateException("called isRunning on empty LottieDrawable");
- }
- return nIsRunning(mNativePtr);
- }
-
- @Override
- public int getOpacity() {
- // We assume translucency to avoid checking each pixel.
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public void setAlpha(int alpha) {
- //TODO
- }
-
- @Override
- public void setColorFilter(@Nullable ColorFilter colorFilter) {
- //TODO
- }
-
- private static native long nCreate(String json);
- private static native void nDraw(long nativeInstance, long nativeCanvas);
- @FastNative
- private static native long nGetNativeFinalizer();
- @FastNative
- private static native long nNativeByteSize(long nativeInstance);
- @FastNative
- private static native boolean nIsRunning(long nativeInstance);
- @FastNative
- private static native boolean nStart(long nativeInstance);
- @FastNative
- private static native boolean nStop(long nativeInstance);
-
-}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 1a878df..4d0a058 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -25,7 +25,6 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.ValueAnimator;
-import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -993,9 +992,9 @@
RippleShader shader = new RippleShader();
// Grab the color for the current state and cut the alpha channel in
// half so that the ripple and background together yield full alpha.
- final int color = clampAlpha(mMaskColorFilter == null
+ final int color = mMaskColorFilter == null
? mState.mColor.getColorForState(getState(), Color.BLACK)
- : mMaskColorFilter.getColor());
+ : mMaskColorFilter.getColor();
final int effectColor = mState.mEffectColor.getColorForState(getState(), Color.MAGENTA);
final float noisePhase = AnimationUtils.currentAnimationTimeMillis();
shader.setColor(color, effectColor);
@@ -1018,13 +1017,6 @@
return properties;
}
- private int clampAlpha(@ColorInt int color) {
- if (Color.alpha(color) < 128) {
- return (color & 0x00FFFFFF) | 0x80000000;
- }
- return color;
- }
-
@Override
public void invalidateSelf() {
invalidateSelf(true);
@@ -1239,7 +1231,7 @@
// Grab the color for the current state and cut the alpha channel in
// half so that the ripple and background together yield full alpha.
- final int color = clampAlpha(mState.mColor.getColorForState(getState(), Color.BLACK));
+ final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
final Paint p = mRipplePaint;
if (mMaskColorFilter != null) {
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 b13c672..57ba6bb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -99,7 +99,7 @@
ActivityEmbeddingComponent {
static final String TAG = "SplitController";
static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
@VisibleForTesting
@GuardedBy("mLock")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 5d38494..38f1e28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -31,11 +31,12 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.RemoteException;
+import android.util.FloatProperty;
+import android.util.TypedValue;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
@@ -43,9 +44,10 @@
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.annotations.ShellMainThread;
/** Class that defines cross-activity animation. */
@@ -56,24 +58,40 @@
*/
private static final float MIN_WINDOW_SCALE = 0.9f;
- /**
- * Minimum alpha of the closing/entering window.
- */
- private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f;
-
- /**
- * Progress value to fly out closing window and fly in entering window.
- */
- private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f;
-
- /** Max window translation in the Y axis. */
- private static final int WINDOW_MAX_DELTA_Y = 160;
-
- /** Duration of fade in/out entering window. */
- private static final int FADE_IN_DURATION = 100;
/** Duration of post animation after gesture committed. */
private static final int POST_ANIMATION_DURATION = 350;
- private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED;
+ private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
+ private static final FloatProperty<CrossActivityAnimation> ENTER_PROGRESS_PROP =
+ new FloatProperty<>("enter-alpha") {
+ @Override
+ public void setValue(CrossActivityAnimation anim, float value) {
+ anim.setEnteringProgress(value);
+ }
+
+ @Override
+ public Float get(CrossActivityAnimation object) {
+ return object.getEnteringProgress();
+ }
+ };
+ private static final FloatProperty<CrossActivityAnimation> LEAVE_PROGRESS_PROP =
+ new FloatProperty<>("leave-alpha") {
+ @Override
+ public void setValue(CrossActivityAnimation anim, float value) {
+ anim.setLeavingProgress(value);
+ }
+
+ @Override
+ public Float get(CrossActivityAnimation object) {
+ return object.getLeavingProgress();
+ }
+ };
+ private static final float MIN_WINDOW_ALPHA = 0.01f;
+ private static final float WINDOW_X_SHIFT_DP = 96;
+ private static final int SCALE_FACTOR = 100;
+ // TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
+ private static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
+ private static final float TARGET_COMMIT_PROGRESS = 0.5f;
+ private static final float ENTER_ALPHA_THRESHOLD = 0.22f;
private final Rect mStartTaskRect = new Rect();
private final float mCornerRadius;
@@ -84,12 +102,13 @@
// The entering window properties.
private final Rect mEnteringStartRect = new Rect();
private final RectF mEnteringRect = new RectF();
+ private final SpringAnimation mEnteringProgressSpring;
+ private final SpringAnimation mLeavingProgressSpring;
+ // Max window x-shift in pixels.
+ private final float mWindowXShift;
- private float mCurrentAlpha = 1.0f;
-
- private float mEnteringMargin = 0;
- private ValueAnimator mEnteringAnimator;
- private boolean mEnteringWindowShow = false;
+ private float mEnteringProgress = 0f;
+ private float mLeavingProgress = 0f;
private final PointF mInitialTouchPos = new PointF();
@@ -115,16 +134,44 @@
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
mBackground = background;
+ mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
+ mEnteringProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mLeavingProgressSpring = new SpringAnimation(this, LEAVE_PROGRESS_PROP);
+ mLeavingProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mWindowXShift = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, WINDOW_X_SHIFT_DP,
+ context.getResources().getDisplayMetrics());
}
+ /**
+ * Returns 1 if x >= edge1, 0 if x <= edge0, and a smoothed value between the two.
+ * From https://en.wikipedia.org/wiki/Smoothstep
+ */
+ private static float smoothstep(float edge0, float edge1, float x) {
+ if (x < edge0) return 0;
+ if (x >= edge1) return 1;
+
+ x = (x - edge0) / (edge1 - edge0);
+ return x * x * (3 - 2 * x);
+ }
+
+ /**
+ * Linearly map x from range (a1, a2) to range (b1, b2).
+ */
+ private static float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
+ }
+
+ /**
+ * Linearly map a normalized value from (0, 1) to (min, max).
+ */
private static float mapRange(float value, float min, float max) {
return min + (value * (max - min));
}
- private float getInterpolatedProgress(float backProgress) {
- return INTERPOLATOR.getInterpolation(backProgress);
- }
-
private void startBackAnimation() {
if (mEnteringTarget == null || mClosingTarget == null) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
@@ -169,9 +216,6 @@
mBackInProgress = false;
mTransformMatrix.reset();
mInitialTouchPos.set(0, 0);
- mEnteringWindowShow = false;
- mEnteringMargin = 0;
- mEnteringAnimator = null;
if (mFinishCallback != null) {
try {
@@ -181,6 +225,10 @@
}
mFinishCallback = null;
}
+ mEnteringProgressSpring.animateToFinalPosition(0);
+ mEnteringProgressSpring.skipToEnd();
+ mLeavingProgressSpring.animateToFinalPosition(0);
+ mLeavingProgressSpring.skipToEnd();
}
private void onGestureProgress(@NonNull BackEvent backEvent) {
@@ -190,84 +238,12 @@
}
mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
- if (mEnteringTarget == null || mClosingTarget == null) {
- return;
- }
-
- final float progress = getInterpolatedProgress(backEvent.getProgress());
- final float touchY = mTouchPos.y;
-
- final int width = mStartTaskRect.width();
- final int height = mStartTaskRect.height();
-
- final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE);
-
- final float closingWidth = closingScale * width;
- final float closingHeight = (float) height / width * closingWidth;
-
- // Move the window along the X axis.
- final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
-
- // Move the window along the Y axis.
- final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
- final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
- final float closingTop = (height - closingHeight) * 0.5f + deltaY;
- mClosingRect.set(
- closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
- mEnteringRect.set(mClosingRect);
-
- // Switch closing/entering targets while reach to the threshold progress.
- if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) {
- return;
- }
-
- // Present windows and update the alpha.
- mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA);
- mClosingRect.offset(mEnteringMargin, 0);
- mEnteringRect.offset(mEnteringMargin - width, 0);
-
- applyTransform(
- mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha);
- applyTransform(
- mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f);
- mTransaction.apply();
- }
-
- private boolean showEnteringWindow(boolean show) {
- if (mEnteringAnimator == null) {
- mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION);
- mEnteringAnimator.setInterpolator(new AccelerateInterpolator());
- mEnteringAnimator.addUpdateListener(animation -> {
- float progress = animation.getAnimatedFraction();
- final int width = mStartTaskRect.width();
- mEnteringMargin = width * progress;
- // We don't animate to 0 or the surface would become invisible and lose focus.
- final float alpha = progress >= 0.5f ? 0.01f
- : mapRange(progress * 2, mCurrentAlpha, 0.01f);
- mClosingRect.offset(mEnteringMargin, 0);
- mEnteringRect.offset(mEnteringMargin - width, 0);
-
- applyTransform(mClosingTarget.leash, mClosingRect, alpha);
- applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha);
- mTransaction.apply();
- });
- }
-
- if (mEnteringAnimator.isRunning()) {
- return true;
- }
-
- if (mEnteringWindowShow == show) {
- return false;
- }
-
- mEnteringWindowShow = show;
- if (show) {
- mEnteringAnimator.start();
- } else {
- mEnteringAnimator.reverse();
- }
- return true;
+ float progress = backEvent.getProgress();
+ float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
+ ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+ : mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
+ mLeavingProgressSpring.animateToFinalPosition(springProgress);
+ mEnteringProgressSpring.animateToFinalPosition(springProgress);
}
private void onGestureCommitted() {
@@ -275,11 +251,9 @@
finishAnimation();
return;
}
-
- // End the fade in animation.
- if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) {
- mEnteringAnimator.cancel();
- }
+ // End the fade animations
+ mLeavingProgressSpring.cancel();
+ mEnteringProgressSpring.cancel();
// We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
// coordinate of the gesture driven phase.
@@ -309,12 +283,79 @@
float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
- float alpha = mapRange(progress, mCurrentAlpha, 1.0f);
+ float alpha = mapRange(progress, mEnteringProgress, 1.0f);
mEnteringRect.set(left, top, left + width, top + height);
applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
}
+ private float getEnteringProgress() {
+ return mEnteringProgress * SCALE_FACTOR;
+ }
+
+ private void setEnteringProgress(float value) {
+ mEnteringProgress = value / SCALE_FACTOR;
+ if (mEnteringTarget != null && mEnteringTarget.leash != null) {
+ transformWithProgress(
+ mEnteringProgress,
+ Math.max(
+ smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
+ MIN_WINDOW_ALPHA), /* alpha */
+ mEnteringTarget.leash,
+ mEnteringRect,
+ -mWindowXShift,
+ 0
+ );
+ }
+ }
+
+ private float getLeavingProgress() {
+ return mLeavingProgress * SCALE_FACTOR;
+ }
+
+ private void setLeavingProgress(float value) {
+ mLeavingProgress = value / SCALE_FACTOR;
+ if (mClosingTarget != null && mClosingTarget.leash != null) {
+ transformWithProgress(
+ mLeavingProgress,
+ Math.max(
+ 1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
+ MIN_WINDOW_ALPHA),
+ mClosingTarget.leash,
+ mClosingRect,
+ 0,
+ mWindowXShift
+ );
+ }
+ }
+
+ private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
+ RectF targetRect, float deltaXMin, float deltaXMax) {
+ final float touchY = mTouchPos.y;
+
+ final int width = mStartTaskRect.width();
+ final int height = mStartTaskRect.height();
+
+ final float interpolatedProgress = INTERPOLATOR.getInterpolation(progress);
+ final float closingScale = MIN_WINDOW_SCALE
+ + (1 - interpolatedProgress) * (1 - MIN_WINDOW_SCALE);
+ final float closingWidth = closingScale * width;
+ final float closingHeight = (float) height / width * closingWidth;
+
+ // Move the window along the X axis.
+ float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+ closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax);
+
+ // Move the window along the Y axis.
+ final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+ final float closingTop = (height - closingHeight) * 0.5f;
+ targetRect.set(
+ closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+
+ applyTransform(surface, targetRect, Math.max(alpha, MIN_WINDOW_ALPHA));
+ mTransaction.apply();
+ }
+
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
@@ -330,10 +371,12 @@
@Override
public void onBackCancelled() {
// End the fade in animation.
- if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) {
- mEnteringAnimator.cancel();
- }
mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+ mEnteringProgressSpring.cancel();
+ mLeavingProgressSpring.cancel();
+ // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
+ mProgressAnimator.reset();
+ finishAnimation();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 96efeeb..8484013 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -96,8 +96,7 @@
/**
* Different from {@link #equals(Object)}, this method compares the basic geometry properties
- * of two {@link DisplayLayout} objects including width, height, rotation, density, cutout and
- * insets.
+ * of two {@link DisplayLayout} objects including width, height, rotation, density, cutout.
* @return {@code true} if the given {@link DisplayLayout} is identical geometry wise.
*/
public boolean isSameGeometry(@NonNull DisplayLayout other) {
@@ -105,8 +104,7 @@
&& mHeight == other.mHeight
&& mRotation == other.mRotation
&& mDensityDpi == other.mDensityDpi
- && Objects.equals(mCutout, other.mCutout)
- && Objects.equals(mStableInsets, other.mStableInsets);
+ && Objects.equals(mCutout, other.mCutout);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index b9caf62..26f47fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -114,7 +114,9 @@
t.setPosition(leash, positionInParent.x, positionInParent.y);
t.setAlpha(leash, 1f);
t.setMatrix(leash, 1, 0, 0, 1);
- t.show(leash);
+ if (taskInfo.isVisible) {
+ t.show(leash);
+ }
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index e83854e..525beb1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -616,7 +616,7 @@
return;
}
int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
- onDisplayChanged(
+ onDisplayChangedUncheck(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
@@ -702,9 +702,12 @@
}
private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
- if (mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
- return;
+ if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
+ onDisplayChangedUncheck(layout, saveRestoreSnapFraction);
}
+ }
+
+ private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) {
Runnable updateDisplayLayout = () -> {
final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
&& mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 618c446..36ce0a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -445,7 +445,6 @@
backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
backgroundColorForTransition);
- boolean delayedEdgeExtension = false;
if (!isTask && a.hasExtension()) {
if (!Transitions.isOpeningType(change.getMode())) {
// Can screenshot now (before startTransaction is applied)
@@ -455,7 +454,6 @@
// may not be visible or ready yet.
postStartTransactionCallbacks
.add(t -> edgeExtendWindow(change, a, t, finishTransaction));
- delayedEdgeExtension = true;
}
}
@@ -464,19 +462,9 @@
: new Rect(change.getEndAbsBounds());
clipRect.offsetTo(0, 0);
- if (delayedEdgeExtension) {
- // If the edge extension needs to happen after the startTransition has been
- // applied, then we want to only start the animation after the edge extension
- // postStartTransaction callback has been run
- postStartTransactionCallbacks.add(t ->
- startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, mAnimExecutor,
- change.getEndRelOffset(), cornerRadius, clipRect));
- } else {
- startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, mAnimExecutor,
- change.getEndRelOffset(), cornerRadius, clipRect);
- }
+ buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, change.getEndRelOffset(), cornerRadius,
+ clipRect);
if (info.getAnimationOptions() != null) {
attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(),
@@ -490,19 +478,25 @@
startTransaction, finishTransaction);
}
- // postStartTransactionCallbacks require that the start transaction is already
- // applied to run otherwise they may result in flickers and UI inconsistencies.
- boolean waitForStartTransactionApply = postStartTransactionCallbacks.size() > 0;
- startTransaction.apply(waitForStartTransactionApply);
-
- // Run tasks that require startTransaction to already be applied
- for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
- postStartTransactionCallbacks) {
- final SurfaceControl.Transaction t = mTransactionPool.acquire();
- postStartTransactionCallback.accept(t);
- t.apply();
- mTransactionPool.release(t);
+ if (postStartTransactionCallbacks.size() > 0) {
+ // postStartTransactionCallbacks require that the start transaction is already
+ // applied to run otherwise they may result in flickers and UI inconsistencies.
+ startTransaction.apply(true /* sync */);
+ // startTransaction is empty now, so fill it with the edge-extension setup
+ for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+ postStartTransactionCallbacks) {
+ postStartTransactionCallback.accept(startTransaction);
+ }
}
+ startTransaction.apply();
+
+ // now start animations. they are started on another thread, so we have to post them
+ // *after* applying the startTransaction
+ mAnimExecutor.execute(() -> {
+ for (int i = 0; i < animations.size(); ++i) {
+ animations.get(i).start();
+ }
+ });
mRotator.cleanUp(finishTransaction);
TransitionMetrics.getInstance().reportAnimationStart(transition);
@@ -539,8 +533,8 @@
animations.removeAll(animGroupStore);
onAnimFinish.run();
};
- anim.startAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
- mMainExecutor, mAnimExecutor);
+ anim.buildAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
+ mMainExecutor);
for (int i = animGroup.size() - 1; i >= 0; i--) {
final Animator animator = animGroup.get(i);
animGroupStore.add(animator);
@@ -633,11 +627,12 @@
return a;
}
- static void startSurfaceAnimation(@NonNull ArrayList<Animator> animations,
+ /** Builds an animator for the surface and adds it to the `animations` list. */
+ static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Animation anim, @NonNull SurfaceControl leash,
@NonNull Runnable finishCallback, @NonNull TransactionPool pool,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor,
- @Nullable Point position, float cornerRadius, @Nullable Rect clipRect) {
+ @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
+ @Nullable Rect clipRect) {
final SurfaceControl.Transaction transaction = pool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
final Transformation transformation = new Transformation();
@@ -691,7 +686,6 @@
}
});
animations.add(va);
- animExecutor.execute(va::start);
}
private void attachThumbnail(@NonNull ArrayList<Animator> animations,
@@ -745,9 +739,8 @@
};
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
- cornerRadius, change.getEndAbsBounds());
+ buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
+ mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
}
private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
@@ -770,9 +763,8 @@
};
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
- startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
- cornerRadius, change.getEndAbsBounds());
+ buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
+ mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
}
private static int getWallpaperTransitType(TransitionInfo info) {
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 665267f..e643170 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
@@ -21,7 +21,7 @@
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
-import static com.android.wm.shell.transition.DefaultTransitionHandler.startSurfaceAnimation;
+import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurfaceAnimation;
import static com.android.wm.shell.transition.Transitions.TAG;
import android.animation.Animator;
@@ -244,11 +244,11 @@
}
/**
- * Returns true if animating.
+ * Returns true if any animations were added to `animations`.
*/
- public boolean startAnimation(@NonNull ArrayList<Animator> animations,
+ boolean buildAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, float animationScale,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor mainExecutor) {
if (mScreenshotLayer == null) {
// Can't do animation.
return false;
@@ -311,13 +311,11 @@
mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
- startScreenshotAlphaAnimation(animations, finishCallback, mainExecutor,
- animExecutor);
- startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
+ buildScreenshotAlphaAnimation(animations, finishCallback, mainExecutor);
+ startDisplayRotation(animations, finishCallback, mainExecutor);
} else {
- startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
- startScreenshotRotationAnimation(animations, finishCallback, mainExecutor,
- animExecutor);
+ startDisplayRotation(animations, finishCallback, mainExecutor);
+ startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
//startColorAnimation(mTransaction, animationScale);
}
@@ -325,27 +323,24 @@
}
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
- @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
- @NonNull ShellExecutor animExecutor) {
- startSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
- mTransactionPool, mainExecutor, animExecutor, null /* position */,
- 0 /* cornerRadius */, null /* clipRect */);
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+ buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
+ mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
+ null /* clipRect */);
}
private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
- @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
- @NonNull ShellExecutor animExecutor) {
- startSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
- mTransactionPool, mainExecutor, animExecutor, null /* position */,
- 0 /* cornerRadius */, null /* clipRect */);
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+ buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
+ mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
+ null /* clipRect */);
}
- private void startScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
- @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
- @NonNull ShellExecutor animExecutor) {
- startSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
- mTransactionPool, mainExecutor, animExecutor, null /* position */,
- 0 /* cornerRadius */, null /* clipRect */);
+ private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+ buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
+ mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
+ null /* clipRect */);
}
private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index fc2a828..44d6a0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -81,7 +81,7 @@
/** Set to {@code true} to enable shell transitions. */
public static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
&& SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 8a694f7..7aa40e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -70,6 +70,18 @@
@Presubmit
@Test
+ override fun pipAppWindowAlwaysVisible() {
+ // In gestural nav the pip will first move behind home and then above home. The visual
+ // appearance visible->invisible->visible is asserted by pipAppLayerAlwaysVisible().
+ // But the internal states of activity don't need to follow that, such as a temporary
+ // visibility state can be changed quickly outside a transaction so the test doesn't
+ // detect that. Hence, skip the case to avoid restricting the internal implementation.
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppWindowAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
override fun pipAppLayerAlwaysVisible() {
if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
else {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
new file mode 100644
index 0000000..e133443
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Test minimizing a pip window via pinch in gesture. */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } }
+
+ /** Checks that the visible region area of [pipApp] always decreases during the animation. */
+ @Postsubmit
+ @Test
+ fun pipLayerAreaDecreases() {
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index ce31aac..415c270 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -18,7 +18,7 @@
import android.app.Instrumentation
import android.content.Intent
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.PipAppHelper
@@ -80,7 +80,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun hasAtMostOnePipDismissOverlayWindow() {
val matcher = ComponentNameMatcher("", "pip-dismiss-overlay")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index c08ad69..70a1523 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
@@ -136,7 +137,7 @@
}
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest(bugId = 264241018)
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index ff1d2990..d5bb901 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -28,9 +28,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -82,13 +84,14 @@
@Mock
SyncTransactionQueue mSyncQueue;
@Mock
- TaskViewTransitions mTaskViewTransitions;
+ Transitions mTransitions;
SurfaceSession mSession;
SurfaceControl mLeash;
Context mContext;
TaskView mTaskView;
+ TaskViewTransitions mTaskViewTransitions;
@Before
public void setUp() {
@@ -118,6 +121,10 @@
return null;
}).when(mSyncQueue).runInSync(any());
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ doReturn(true).when(mTransitions).isRegistered();
+ }
+ mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
mTaskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
mTaskView.setListener(mExecutor, mViewListener);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 65e1ea8..0bb809d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -29,11 +29,13 @@
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -45,6 +47,8 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -57,6 +61,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -65,6 +70,8 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -98,11 +105,7 @@
@Mock
private DisplayInsetsController mDisplayInsetsController;
@Mock
- private Transitions mTransitions;
- @Mock
private TransactionPool mTransactionPool;
- @Mock
- private ShellExecutor mMainExecutor;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -112,11 +115,16 @@
private SurfaceControl mRootLeash;
private ActivityManager.RunningTaskInfo mRootTask;
private StageCoordinator mStageCoordinator;
+ private Transitions mTransitions;
+ private final TestShellExecutor mMainExecutor = new TestShellExecutor();
+ private final ShellExecutor mAnimExecutor = new TestShellExecutor();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@Before
@UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
+ mTransitions = createTestTransitions();
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
@@ -329,7 +337,20 @@
mStageCoordinator.onFoldedStateChanged(true);
- verify(mStageCoordinator).onSplitScreenExit();
- verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
+ } else {
+ verify(mStageCoordinator).onSplitScreenExit();
+ verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ }
+ }
+
+ private Transitions createTestTransitions() {
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ shellInit.init();
+ return t;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 5ee8bf3..1a1bebd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -61,7 +61,7 @@
@RunWith(AndroidJUnit4.class)
public final class StageTaskListenerTests extends ShellTestCase {
private static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
@Mock
private ShellTaskOrganizer mTaskOrganizer;
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 9112b1b..8d4bda2 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -78,7 +78,6 @@
"external/skia/src/utils",
"external/skia/src/gpu",
"external/skia/src/shaders",
- "external/skia/modules/skottie",
],
},
host: {
@@ -388,7 +387,6 @@
"external/skia/src/effects",
"external/skia/src/image",
"external/skia/src/images",
- "external/skia/modules/skottie",
],
shared_libs: [
@@ -420,7 +418,6 @@
"jni/BitmapRegionDecoder.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
- "jni/LottieDrawable.cpp",
"jni/Movie.cpp",
"jni/MovieImpl.cpp",
"jni/pdf/PdfDocument.cpp",
@@ -528,7 +525,6 @@
"hwui/BlurDrawLooper.cpp",
"hwui/Canvas.cpp",
"hwui/ImageDecoder.cpp",
- "hwui/LottieDrawable.cpp",
"hwui/MinikinSkia.cpp",
"hwui/MinikinUtils.cpp",
"hwui/PaintImpl.cpp",
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index cb385d4..3f7c4f0 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -21,6 +21,7 @@
#include <hwui/Paint.h>
#include <experimental/type_traits>
+#include <log/log.h>
#include <utility>
#include "SkAndroidFrameworkUtils.h"
@@ -44,6 +45,7 @@
#include "SkVertices.h"
#include "VectorDrawable.h"
#include "include/gpu/GpuTypes.h" // from Skia
+#include "include/gpu/GrDirectContext.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
@@ -65,16 +67,24 @@
template <typename S, typename... Rest>
static void copy_v(void* dst, const S* src, int n, Rest&&... rest) {
- SkASSERTF(((uintptr_t)dst & (alignof(S) - 1)) == 0,
- "Expected %p to be aligned for at least %zu bytes.", dst, alignof(S));
- sk_careful_memcpy(dst, src, n * sizeof(S));
- copy_v(SkTAddOffset<void>(dst, n * sizeof(S)), std::forward<Rest>(rest)...);
+ LOG_FATAL_IF(((uintptr_t)dst & (alignof(S) - 1)) != 0,
+ "Expected %p to be aligned for at least %zu bytes.",
+ dst, alignof(S));
+ // If n is 0, there is nothing to copy into dst from src.
+ if (n > 0) {
+ memcpy(dst, src, n * sizeof(S));
+ dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(dst) + n * sizeof(S));
+ }
+ // Repeat for the next items, if any
+ copy_v(dst, std::forward<Rest>(rest)...);
}
// Helper for getting back at arrays which have been copy_v'd together after an Op.
template <typename D, typename T>
static const D* pod(const T* op, size_t offset = 0) {
- return SkTAddOffset<const D>(op + 1, offset);
+ return reinterpret_cast<const D*>(
+ reinterpret_cast<const uint8_t*>(op + 1) + offset);
}
namespace {
@@ -457,12 +467,43 @@
struct DrawMesh final : Op {
static const auto kType = Type::DrawMesh;
DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
- : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+ : cpuMesh(mesh), blender(std::move(blender)), paint(paint) {
+ isGpuBased = false;
+ }
- SkMesh mesh;
+ SkMesh cpuMesh;
+ mutable SkMesh gpuMesh;
sk_sp<SkBlender> blender;
SkPaint paint;
- void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh, blender, paint); }
+ mutable bool isGpuBased;
+ mutable GrDirectContext::DirectContextID contextId;
+ void draw(SkCanvas* c, const SkMatrix&) const {
+ GrDirectContext* directContext = c->recordingContext()->asDirectContext();
+ GrDirectContext::DirectContextID id = directContext->directContextID();
+ if (!isGpuBased || contextId != id) {
+ sk_sp<SkMesh::VertexBuffer> vb =
+ SkMesh::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
+ if (!cpuMesh.indexBuffer()) {
+ gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(),
+ cpuMesh.vertexOffset(), cpuMesh.refUniforms(),
+ cpuMesh.bounds())
+ .mesh;
+ } else {
+ sk_sp<SkMesh::IndexBuffer> ib =
+ SkMesh::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
+ gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb,
+ cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib,
+ cpuMesh.indexCount(), cpuMesh.indexOffset(),
+ cpuMesh.refUniforms(), cpuMesh.bounds())
+ .mesh;
+ }
+
+ isGpuBased = true;
+ contextId = id;
+ }
+
+ c->drawMesh(gpuMesh, blender, paint);
+ }
};
struct DrawAtlas final : Op {
static const auto kType = Type::DrawAtlas;
@@ -612,7 +653,7 @@
template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {
size_t skip = SkAlignPtr(sizeof(T) + pod);
- SkASSERT(skip < (1 << 24));
+ LOG_FATAL_IF(skip >= (1 << 24));
if (fUsed + skip > fReserved) {
static_assert(is_power_of_two(SKLITEDL_PAGE),
"This math needs updating for non-pow2.");
@@ -621,7 +662,7 @@
fBytes.realloc(fReserved);
LOG_ALWAYS_FATAL_IF(fBytes.get() == nullptr, "realloc(%zd) failed", fReserved);
}
- SkASSERT(fUsed + skip <= fReserved);
+ LOG_FATAL_IF((fUsed + skip) > fReserved);
auto op = (T*)(fBytes.get() + fUsed);
fUsed += skip;
new (op) T{std::forward<Args>(args)...};
@@ -751,7 +792,7 @@
int fs = lattice.fRectTypes ? (xs + 1) * (ys + 1) : 0;
size_t bytes = (xs + ys) * sizeof(int) + fs * sizeof(SkCanvas::Lattice::RectType) +
fs * sizeof(SkColor);
- SkASSERT(lattice.fBounds);
+ LOG_FATAL_IF(!lattice.fBounds);
void* pod = this->push<DrawImageLattice>(bytes, std::move(image), xs, ys, fs, *lattice.fBounds,
dst, filter, paint, palette);
copy_v(pod, lattice.fXDivs, xs, lattice.fYDivs, ys, lattice.fColors, fs, lattice.fRectTypes,
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index af8bd26..d0124f5 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -19,6 +19,8 @@
#include <SkAndroidFrameworkUtils.h>
#include <SkAnimatedImage.h>
#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
#include <SkCanvasPriv.h>
#include <SkCanvasStateUtils.h>
#include <SkColorFilter.h>
@@ -35,7 +37,6 @@
#include <SkRect.h>
#include <SkRefCnt.h>
#include <SkShader.h>
-#include <SkTemplates.h>
#include <SkTextBlob.h>
#include <SkVertices.h>
@@ -45,13 +46,14 @@
#include "CanvasProperty.h"
#include "NinePatchUtils.h"
-#include "SkBlendMode.h"
#include "VectorDrawable.h"
#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
#include "hwui/PaintFilter.h"
+#include <log/log.h>
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/HolePunch.h"
+#include <ui/FatVector.h>
namespace android {
@@ -248,7 +250,7 @@
? static_cast<const SaveRec*>(&mSaveStack->back())
: nullptr;
int currentSaveCount = mCanvas->getSaveCount();
- SkASSERT(!rec || currentSaveCount >= rec->saveCount);
+ LOG_FATAL_IF(!(!rec || currentSaveCount >= rec->saveCount));
return (rec && rec->saveCount == currentSaveCount) ? rec : nullptr;
}
@@ -298,7 +300,7 @@
// Applies and optionally removes all clips >= index.
void SkiaCanvas::applyPersistentClips(size_t clipStartIndex) {
- SkASSERT(clipStartIndex <= mClipStack.size());
+ LOG_FATAL_IF(clipStartIndex > mClipStack.size());
const auto begin = mClipStack.cbegin() + clipStartIndex;
const auto end = mClipStack.cend();
@@ -646,7 +648,7 @@
texsPtr += 1;
y += dy;
}
- SkASSERT(texsPtr - texs == ptCount);
+ LOG_FATAL_IF((texsPtr - texs) != ptCount);
}
// cons up indices
@@ -669,14 +671,14 @@
// bump to the next row
index += 1;
}
- SkASSERT(indexPtr - indices == indexCount);
+ LOG_FATAL_IF((indexPtr - indices) != indexCount);
}
// double-check that we have legal indices
-#ifdef SK_DEBUG
+#if !defined(NDEBUG)
{
for (int i = 0; i < indexCount; i++) {
- SkASSERT((unsigned)indices[i] < (unsigned)ptCount);
+ LOG_FATAL_IF((unsigned)indices[i] >= (unsigned)ptCount);
}
}
#endif
@@ -718,10 +720,12 @@
numFlags = (lattice.fXCount + 1) * (lattice.fYCount + 1);
}
- SkAutoSTMalloc<25, SkCanvas::Lattice::RectType> flags(numFlags);
- SkAutoSTMalloc<25, SkColor> colors(numFlags);
+ // Most times, we do not have very many flags/colors, so the stack allocated part of
+ // FatVector will save us a heap allocation.
+ FatVector<SkCanvas::Lattice::RectType, 25> flags(numFlags);
+ FatVector<SkColor, 25> colors(numFlags);
if (numFlags > 0) {
- NinePatchUtils::SetLatticeFlags(&lattice, flags.get(), numFlags, chunk, colors.get());
+ NinePatchUtils::SetLatticeFlags(&lattice, flags.data(), numFlags, chunk, colors.data());
}
lattice.fBounds = nullptr;
@@ -736,10 +740,6 @@
return imgDrawable->drawStaging(mCanvas);
}
-void SkiaCanvas::drawLottie(LottieDrawable* lottieDrawable) {
- lottieDrawable->drawStaging(mCanvas);
-}
-
void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
vectorDrawable->drawStaging(this);
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 1524dff..f2c286a 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -144,7 +144,6 @@
float dstTop, float dstRight, float dstBottom,
const Paint* paint) override;
virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override;
- virtual void drawLottie(LottieDrawable* lottieDrawable) override;
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index b1aa1947..f57d80c 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -37,7 +37,6 @@
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*);
-extern int register_android_graphics_drawable_LottieDrawable(JNIEnv*);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
@@ -118,7 +117,6 @@
REG_JNI(register_android_graphics_HardwareRendererObserver),
REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
- REG_JNI(register_android_graphics_drawable_LottieDrawable),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 07e2fe2..2a20191 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -60,7 +60,6 @@
typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc;
class AnimatedImageDrawable;
-class LottieDrawable;
class Bitmap;
class Paint;
struct Typeface;
@@ -243,7 +242,6 @@
const Paint* paint) = 0;
virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0;
- virtual void drawLottie(LottieDrawable* lottieDrawable) = 0;
virtual void drawPicture(const SkPicture& picture) = 0;
/**
diff --git a/libs/hwui/hwui/LottieDrawable.cpp b/libs/hwui/hwui/LottieDrawable.cpp
deleted file mode 100644
index 92dc51e..0000000
--- a/libs/hwui/hwui/LottieDrawable.cpp
+++ /dev/null
@@ -1,83 +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.
- */
-
-#include "LottieDrawable.h"
-
-#include <SkTime.h>
-#include <log/log.h>
-#include <pipeline/skia/SkiaUtils.h>
-
-namespace android {
-
-sk_sp<LottieDrawable> LottieDrawable::Make(sk_sp<skottie::Animation> animation, size_t bytesUsed) {
- if (animation) {
- return sk_sp<LottieDrawable>(new LottieDrawable(std::move(animation), bytesUsed));
- }
- return nullptr;
-}
-LottieDrawable::LottieDrawable(sk_sp<skottie::Animation> animation, size_t bytesUsed)
- : mAnimation(std::move(animation)), mBytesUsed(bytesUsed) {}
-
-bool LottieDrawable::start() {
- if (mRunning) {
- return false;
- }
-
- mRunning = true;
- return true;
-}
-
-bool LottieDrawable::stop() {
- bool wasRunning = mRunning;
- mRunning = false;
- return wasRunning;
-}
-
-bool LottieDrawable::isRunning() {
- return mRunning;
-}
-
-// TODO: Check to see if drawable is actually dirty
-bool LottieDrawable::isDirty() {
- return true;
-}
-
-void LottieDrawable::onDraw(SkCanvas* canvas) {
- if (mRunning) {
- const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-
- nsecs_t t = 0;
- if (mStartTime == 0) {
- mStartTime = currentTime;
- } else {
- t = currentTime - mStartTime;
- }
- double seekTime = std::fmod((double)t * 1e-9, mAnimation->duration());
- mAnimation->seekFrameTime(seekTime);
- mAnimation->render(canvas);
- }
-}
-
-void LottieDrawable::drawStaging(SkCanvas* canvas) {
- onDraw(canvas);
-}
-
-SkRect LottieDrawable::onGetBounds() {
- // We do not actually know the bounds, so give a conservative answer.
- return SkRectMakeLargest();
-}
-
-} // namespace android
diff --git a/libs/hwui/hwui/LottieDrawable.h b/libs/hwui/hwui/LottieDrawable.h
deleted file mode 100644
index 9cc34bf..0000000
--- a/libs/hwui/hwui/LottieDrawable.h
+++ /dev/null
@@ -1,67 +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.
- */
-
-#pragma once
-
-#include <SkDrawable.h>
-#include <Skottie.h>
-#include <utils/Timers.h>
-
-class SkCanvas;
-
-namespace android {
-
-/**
- * Native component of android.graphics.drawable.LottieDrawable.java.
- * This class can be drawn into Canvas.h and maintains the state needed to drive
- * the animation from the RenderThread.
- */
-class LottieDrawable : public SkDrawable {
-public:
- static sk_sp<LottieDrawable> Make(sk_sp<skottie::Animation> animation, size_t bytes);
-
- // Draw to software canvas
- void drawStaging(SkCanvas* canvas);
-
- // Returns true if the animation was started; false otherwise (e.g. it was
- // already running)
- bool start();
- // Returns true if the animation was stopped; false otherwise (e.g. it was
- // already stopped)
- bool stop();
- bool isRunning();
-
- // TODO: Is dirty should take in a time til next frame to determine if it is dirty
- bool isDirty();
-
- SkRect onGetBounds() override;
-
- size_t byteSize() const { return sizeof(*this) + mBytesUsed; }
-
-protected:
- void onDraw(SkCanvas* canvas) override;
-
-private:
- LottieDrawable(sk_sp<skottie::Animation> animation, size_t bytes_used);
-
- sk_sp<skottie::Animation> mAnimation;
- bool mRunning = false;
- // The start time for the drawable itself.
- nsecs_t mStartTime = 0;
- const size_t mBytesUsed = 0;
-};
-
-} // namespace android
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index ae2e974..f5cd793 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -8,7 +8,6 @@
#include <nativehelper/JNIHelp.h>
#include "GraphicsJNI.h"
-#include "include/private/SkTemplates.h" // SkTAddOffset
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkColorSpace.h"
@@ -21,6 +20,7 @@
#include "SkTypes.h"
#include <cutils/ashmem.h>
#include <hwui/Canvas.h>
+#include <log/log.h>
using namespace android;
@@ -490,7 +490,7 @@
void GraphicsJNI::set_metrics(JNIEnv* env, jobject metrics, const SkFontMetrics& skmetrics) {
if (metrics == nullptr) return;
- SkASSERT(env->IsInstanceOf(metrics, gFontMetrics_class));
+ LOG_FATAL_IF(!env->IsInstanceOf(metrics, gFontMetrics_class));
env->SetFloatField(metrics, gFontMetrics_top, SkScalarToFloat(skmetrics.fTop));
env->SetFloatField(metrics, gFontMetrics_ascent, SkScalarToFloat(skmetrics.fAscent));
env->SetFloatField(metrics, gFontMetrics_descent, SkScalarToFloat(skmetrics.fDescent));
@@ -504,7 +504,7 @@
int leading = SkScalarRoundToInt(skmetrics.fLeading);
if (metrics) {
- SkASSERT(env->IsInstanceOf(metrics, gFontMetricsInt_class));
+ LOG_FATAL_IF(!env->IsInstanceOf(metrics, gFontMetricsInt_class));
env->SetIntField(metrics, gFontMetricsInt_top, SkScalarFloorToInt(skmetrics.fTop));
env->SetIntField(metrics, gFontMetricsInt_ascent, ascent);
env->SetIntField(metrics, gFontMetricsInt_descent, descent);
@@ -713,7 +713,9 @@
mSkiaBitmap->info().height());
for (int y = 0; y < rowsToCopy; y++) {
memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy);
- dst = SkTAddOffset<void>(dst, dstRowBytes);
+ // Cast to bytes in order to apply the dstRowBytes offset correctly.
+ dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(dst) + dstRowBytes);
}
recycledPixels->notifyPixelsChanged();
recycledPixels->unref();
diff --git a/libs/hwui/jni/LottieDrawable.cpp b/libs/hwui/jni/LottieDrawable.cpp
deleted file mode 100644
index fb6eede..0000000
--- a/libs/hwui/jni/LottieDrawable.cpp
+++ /dev/null
@@ -1,91 +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.
- */
-
-#include <SkRect.h>
-#include <Skottie.h>
-#include <hwui/Canvas.h>
-#include <hwui/LottieDrawable.h>
-
-#include "GraphicsJNI.h"
-#include "Utils.h"
-
-using namespace android;
-
-static jclass gLottieDrawableClass;
-
-static jlong LottieDrawable_nCreate(JNIEnv* env, jobject, jstring jjson) {
- const ScopedUtfChars cstr(env, jjson);
- // TODO(b/259267150) provide more accurate byteSize
- size_t bytes = strlen(cstr.c_str());
- auto animation = skottie::Animation::Builder().make(cstr.c_str(), bytes);
- sk_sp<LottieDrawable> drawable(LottieDrawable::Make(std::move(animation), bytes));
- if (!drawable) {
- return 0;
- }
- return reinterpret_cast<jlong>(drawable.release());
-}
-
-static void LottieDrawable_destruct(LottieDrawable* drawable) {
- SkSafeUnref(drawable);
-}
-
-static jlong LottieDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
- return static_cast<jlong>(reinterpret_cast<uintptr_t>(&LottieDrawable_destruct));
-}
-
-static void LottieDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) {
- auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr);
- auto* canvas = reinterpret_cast<Canvas*>(canvasPtr);
- canvas->drawLottie(drawable);
-}
-
-static jboolean LottieDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
- auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr);
- return drawable->isRunning();
-}
-
-static jboolean LottieDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
- auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr);
- return drawable->start();
-}
-
-static jboolean LottieDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
- auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr);
- return drawable->stop();
-}
-
-static jlong LottieDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
- auto* drawable = reinterpret_cast<LottieDrawable*>(nativePtr);
- return drawable->byteSize();
-}
-
-static const JNINativeMethod gLottieDrawableMethods[] = {
- {"nCreate", "(Ljava/lang/String;)J", (void*)LottieDrawable_nCreate},
- {"nNativeByteSize", "(J)J", (void*)LottieDrawable_nNativeByteSize},
- {"nGetNativeFinalizer", "()J", (void*)LottieDrawable_nGetNativeFinalizer},
- {"nDraw", "(JJ)V", (void*)LottieDrawable_nDraw},
- {"nIsRunning", "(J)Z", (void*)LottieDrawable_nIsRunning},
- {"nStart", "(J)Z", (void*)LottieDrawable_nStart},
- {"nStop", "(J)Z", (void*)LottieDrawable_nStop},
-};
-
-int register_android_graphics_drawable_LottieDrawable(JNIEnv* env) {
- gLottieDrawableClass = reinterpret_cast<jclass>(
- env->NewGlobalRef(FindClassOrDie(env, "android/graphics/drawable/LottieDrawable")));
-
- return android::RegisterMethodsOrDie(env, "android/graphics/drawable/LottieDrawable",
- gLottieDrawableMethods, NELEM(gLottieDrawableMethods));
-}
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
index 7a73f2d..61c2260 100644
--- a/libs/hwui/jni/Mesh.h
+++ b/libs/hwui/jni/Mesh.h
@@ -20,6 +20,7 @@
#include <SkMesh.h>
#include <jni.h>
+#include <log/log.h>
#include <utility>
#include "graphics_jni_helpers.h"
@@ -186,23 +187,24 @@
std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
const T& val) {
if (!fVar) {
- SkDEBUGFAIL("Assigning to missing variable");
+ LOG_FATAL("Assigning to missing variable");
} else if (sizeof(val) != fVar->sizeInBytes()) {
- SkDEBUGFAIL("Incorrect value size");
+ LOG_FATAL("Incorrect value size");
} else {
- memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), &val,
- szeof(val));
+ void* dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+ memcpy(dst, &val, sizeof(val));
}
}
MeshUniform& operator=(const SkMatrix& val) {
if (!fVar) {
- SkDEBUGFAIL("Assigning to missing variable");
+ LOG_FATAL("Assigning to missing variable");
} else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
- SkDEBUGFAIL("Incorrect value size");
+ LOG_FATAL("Incorrect value size");
} else {
- float* data =
- SkTAddOffset<float>(fOwner->writableUniformData(), (ptrdiff_t)fVar->offset);
+ float* data = reinterpret_cast<float*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
data[0] = val.get(0);
data[1] = val.get(3);
data[2] = val.get(6);
@@ -220,14 +222,15 @@
bool set(const T val[], const int count) {
static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
if (!fVar) {
- SkDEBUGFAIL("Assigning to missing variable");
+ LOG_FATAL("Assigning to missing variable");
return false;
} else if (sizeof(T) * count != fVar->sizeInBytes()) {
- SkDEBUGFAIL("Incorrect value size");
+ LOG_FATAL("Incorrect value size");
return false;
} else {
- memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), val,
- sizeof(T) * count);
+ void* dst = reinterpret_cast<void*>(
+ reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+ memcpy(dst, val, sizeof(T) * count);
}
return true;
}
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 3f4d004..5892308 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -822,6 +822,11 @@
proxy->notifyCallbackPending();
}
+static void android_view_ThreadedRenderer_notifyExpensiveFrame(JNIEnv*, jclass, jlong proxyPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->notifyExpensiveFrame();
+}
+
// Plumbs the display density down to DeviceInfo.
static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) {
// Convert from dpi to density-independent pixels.
@@ -1000,6 +1005,8 @@
(void*)android_view_ThreadedRenderer_setRtAnimationsEnabled},
{"nNotifyCallbackPending", "(J)V",
(void*)android_view_ThreadedRenderer_notifyCallbackPending},
+ {"nNotifyExpensiveFrame", "(J)V",
+ (void*)android_view_ThreadedRenderer_notifyExpensiveFrame},
};
static JavaVM* mJvm = nullptr;
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index f0dc5eb..fcfc4f8 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -146,16 +146,6 @@
}
}
- for (auto& lottie : mLotties) {
- // If any animated image in the display list needs updated, then damage the node.
- if (lottie->isDirty()) {
- isDirty = true;
- }
- if (lottie->isRunning()) {
- info.out.hasAnimations = true;
- }
- }
-
for (auto& [vectorDrawable, cachedMatrix] : mVectorDrawables) {
// If any vector drawable in the display list needs update, damage the node.
if (vectorDrawable->isDirty()) {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 39217fc..2a67734 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -22,7 +22,6 @@
#include "RenderNodeDrawable.h"
#include "TreeInfo.h"
#include "hwui/AnimatedImageDrawable.h"
-#include "hwui/LottieDrawable.h"
#include "utils/LinearAllocator.h"
#include "utils/Pair.h"
@@ -187,8 +186,6 @@
return mHasHolePunches;
}
- // TODO(b/257304231): create common base class for Lotties and AnimatedImages
- std::vector<LottieDrawable*> mLotties;
std::vector<AnimatedImageDrawable*> mAnimatedImages;
DisplayListData mDisplayList;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index db449d6..c9d79ab 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -16,7 +16,6 @@
#include "SkiaRecordingCanvas.h"
#include "hwui/Paint.h"
-#include <include/private/SkTemplates.h> // SkAutoSTMalloc
#include <SkBlendMode.h>
#include <SkData.h>
#include <SkDrawable.h>
@@ -43,6 +42,8 @@
#include "pipeline/skia/VkFunctorDrawable.h"
#include "pipeline/skia/VkInteropFunctorDrawable.h"
#endif
+#include <log/log.h>
+#include <ui/FatVector.h>
namespace android {
namespace uirenderer {
@@ -55,7 +56,7 @@
void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width,
int height) {
mCurrentBarrier = nullptr;
- SkASSERT(mDisplayList.get() == nullptr);
+ LOG_FATAL_IF(mDisplayList.get() != nullptr);
if (renderNode) {
mDisplayList = renderNode->detachAvailableList();
@@ -188,11 +189,6 @@
#endif
}
-void SkiaRecordingCanvas::drawLottie(LottieDrawable* lottie) {
- drawDrawable(lottie);
- mDisplayList->mLotties.push_back(lottie);
-}
-
void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
mRecorder.drawVectorDrawable(tree);
SkMatrix mat;
@@ -285,10 +281,12 @@
numFlags = (lattice.fXCount + 1) * (lattice.fYCount + 1);
}
- SkAutoSTMalloc<25, SkCanvas::Lattice::RectType> flags(numFlags);
- SkAutoSTMalloc<25, SkColor> colors(numFlags);
+ // Most times, we do not have very many flags/colors, so the stack allocated part of
+ // FatVector will save us a heap allocation.
+ FatVector<SkCanvas::Lattice::RectType, 25> flags(numFlags);
+ FatVector<SkColor, 25> colors(numFlags);
if (numFlags > 0) {
- NinePatchUtils::SetLatticeFlags(&lattice, flags.get(), numFlags, chunk, colors.get());
+ NinePatchUtils::SetLatticeFlags(&lattice, flags.data(), numFlags, chunk, colors.data());
}
lattice.fBounds = nullptr;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index c823d8d..7844e2c 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -78,7 +78,6 @@
uirenderer::CanvasPropertyPaint* paint) override;
virtual void drawRipple(const RippleDrawableParams& params) override;
- virtual void drawLottie(LottieDrawable* lottieDrawable) override;
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
virtual void enableZ(bool enableZ) override;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b769f8d..f223137 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -1021,6 +1021,10 @@
mHintSessionWrapper.sendLoadResetHint();
}
+void CanvasContext::sendLoadIncreaseHint() {
+ mHintSessionWrapper.sendLoadIncreaseHint();
+}
+
void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
mSyncDelayDuration = duration;
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 3f796d9..a274d2f 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -223,6 +223,8 @@
void sendLoadResetHint();
+ void sendLoadIncreaseHint();
+
void setSyncDelayDuration(nsecs_t duration);
private:
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 94c9d94..dece548 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -158,6 +158,11 @@
mLastFrameNotification = now;
}
+void HintSessionWrapper::sendLoadIncreaseHint() {
+ if (!useHintSession()) return;
+ gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_UP));
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index fcbc101..c0f7a57 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -33,6 +33,7 @@
void updateTargetWorkDuration(long targetDurationNanos);
void reportActualWorkDuration(long actualDurationNanos);
void sendLoadResetHint();
+ void sendLoadIncreaseHint();
private:
bool useHintSession();
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ed01e32..5edb0b1 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -254,6 +254,10 @@
mRenderThread.queue().post([this]() { mContext->sendLoadResetHint(); });
}
+void RenderProxy::notifyExpensiveFrame() {
+ mRenderThread.queue().post([this]() { mContext->sendLoadIncreaseHint(); });
+}
+
void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
mRenderThread.queue().runSync([&]() {
std::lock_guard lock(mRenderThread.getJankDataMutex());
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 17cf665..2aafe76 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -112,6 +112,7 @@
void stopDrawing();
void notifyFramePending();
void notifyCallbackPending();
+ void notifyExpensiveFrame();
void dumpProfileInfo(int fd, int dumpFlags);
// Not exported, only used for testing
diff --git a/location/java/android/location/GnssAntennaInfo.java b/location/java/android/location/GnssAntennaInfo.java
index ce73be1..3558dd5 100644
--- a/location/java/android/location/GnssAntennaInfo.java
+++ b/location/java/android/location/GnssAntennaInfo.java
@@ -347,8 +347,8 @@
@Override
public String toString() {
return "SphericalCorrections{"
- + "Corrections=" + Arrays.toString(mCorrections)
- + ", CorrectionUncertainties=" + Arrays.toString(mCorrectionUncertainties)
+ + "Corrections=" + Arrays.deepToString(mCorrections)
+ + ", CorrectionUncertainties=" + Arrays.deepToString(mCorrectionUncertainties)
+ ", DeltaTheta=" + getDeltaTheta()
+ ", DeltaPhi=" + getDeltaPhi()
+ '}';
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 4d3f05b..ceb3858 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -608,6 +608,40 @@
CHANNEL_OUT_LOW_FREQUENCY_2);
// CHANNEL_OUT_ALL is not yet defined; if added then it should match AUDIO_CHANNEL_OUT_ALL
+ /** @hide */
+ @IntDef(flag = true, prefix = "CHANNEL_OUT", value = {
+ CHANNEL_OUT_FRONT_LEFT,
+ CHANNEL_OUT_FRONT_RIGHT,
+ CHANNEL_OUT_FRONT_CENTER,
+ CHANNEL_OUT_LOW_FREQUENCY,
+ CHANNEL_OUT_BACK_LEFT,
+ CHANNEL_OUT_BACK_RIGHT,
+ CHANNEL_OUT_FRONT_LEFT_OF_CENTER,
+ CHANNEL_OUT_FRONT_RIGHT_OF_CENTER,
+ CHANNEL_OUT_BACK_CENTER,
+ CHANNEL_OUT_SIDE_LEFT,
+ CHANNEL_OUT_SIDE_RIGHT,
+ CHANNEL_OUT_TOP_CENTER,
+ CHANNEL_OUT_TOP_FRONT_LEFT,
+ CHANNEL_OUT_TOP_FRONT_CENTER,
+ CHANNEL_OUT_TOP_FRONT_RIGHT,
+ CHANNEL_OUT_TOP_BACK_LEFT,
+ CHANNEL_OUT_TOP_BACK_CENTER,
+ CHANNEL_OUT_TOP_BACK_RIGHT,
+ CHANNEL_OUT_TOP_SIDE_LEFT,
+ CHANNEL_OUT_TOP_SIDE_RIGHT,
+ CHANNEL_OUT_BOTTOM_FRONT_LEFT,
+ CHANNEL_OUT_BOTTOM_FRONT_CENTER,
+ CHANNEL_OUT_BOTTOM_FRONT_RIGHT,
+ CHANNEL_OUT_LOW_FREQUENCY_2,
+ CHANNEL_OUT_FRONT_WIDE_LEFT,
+ CHANNEL_OUT_FRONT_WIDE_RIGHT,
+ CHANNEL_OUT_HAPTIC_B,
+ CHANNEL_OUT_HAPTIC_A
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ChannelOut {}
+
/** Minimum value for sample rate,
* assuming AudioTrack and AudioRecord share the same limitations.
* @hide
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3fbada7..813929e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6442,6 +6442,20 @@
}
/**
+ * Returns the registered volume controller interface.
+ *
+ * @hide
+ */
+ @Nullable
+ public IVolumeController getVolumeController() {
+ try {
+ return getService().getVolumeController();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notify audio manager about volume controller visibility changes.
* Currently limited to SystemUI.
*
@@ -6505,6 +6519,106 @@
/**
* @hide
+ * @return the RS2 value used for momentary exposure warnings
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public float getRs2Value() {
+ try {
+ return getService().getRs2Value();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the RS2 value used for momentary exposure warnings
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public void setRs2Value(float rs2Value) {
+ try {
+ getService().setRs2Value(rs2Value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * @return the current computed sound dose value
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public float getCsd() {
+ try {
+ return getService().getCsd();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the computed sound dose value to {@code csd}
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public void setCsd(float csd) {
+ try {
+ getService().setCsd(csd);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Forces the computation of MEL values (used for CSD) on framework level. This will have the
+ * result of ignoring the MEL values computed on HAL level. Should only be used in testing
+ * since this can affect the certification of a device with EN50332-3 regulation.
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public void forceUseFrameworkMel(boolean useFrameworkMel) {
+ try {
+ getService().forceUseFrameworkMel(useFrameworkMel);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Forces the computation of CSD on all output devices.
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
+ try {
+ getService().forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Returns whether CSD is enabled on this device.
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public boolean isCsdEnabled() {
+ try {
+ return getService().isCsdEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* Sound dose warning at every 100% of dose during integration window
*/
public static final int CSD_WARNING_DOSE_REACHED_1X = 1;
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index f64e5cc..4d6ddfd 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -559,7 +559,8 @@
/**
* @hide
- * Return whether this player's output is spatialized
+ * Return whether this player's output is being processed by the spatializer effect backing
+ * the {@link android.media.Spatializer} implementation.
* @return true if spatialized, false if not or playback hasn't started
*/
@SystemApi
@@ -588,7 +589,7 @@
* the definitions for the <code>CHANNEL_OUT_*</code> values used for the mask's bitfield
*/
@SystemApi
- public int getChannelMask() {
+ public @AudioFormat.ChannelOut int getChannelMask() {
synchronized (mUpdateablePropLock) {
return (AudioFormat.convertNativeChannelMaskToOutMask(mFormatInfo.mNativeChannelMask));
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index c06352c..5ba7891 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -260,6 +260,8 @@
void setVolumeController(in IVolumeController controller);
+ @nullable IVolumeController getVolumeController();
+
void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible);
boolean isStreamAffectedByRingerMode(int streamType);
@@ -270,6 +272,27 @@
void lowerVolumeToRs1(String callingPackage);
+ @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+ float getRs2Value();
+
+ @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+ oneway void setRs2Value(float rs2Value);
+
+ @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+ float getCsd();
+
+ @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+ oneway void setCsd(float csd);
+
+ @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+ oneway void forceUseFrameworkMel(boolean useFrameworkMel);
+
+ @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+ oneway void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices);
+
+ @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+ boolean isCsdEnabled();
+
int setHdmiSystemAudioSupported(boolean on);
boolean isHdmiSystemAudioSupported();
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index fa74a9f..a7959c5 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1515,6 +1515,16 @@
}
/**
+ * Returns the current {@link RoutingSessionInfo} associated to this controller.
+ */
+ @NonNull
+ public RoutingSessionInfo getRoutingSessionInfo() {
+ synchronized (mControllerLock) {
+ return mSessionInfo;
+ }
+ }
+
+ /**
* Gets the information about how volume is handled on the session.
*
* <p>Please note that you may not control the volume of the session even when you can
@@ -1873,13 +1883,6 @@
return result.toString();
}
- @NonNull
- RoutingSessionInfo getRoutingSessionInfo() {
- synchronized (mControllerLock) {
- return mSessionInfo;
- }
- }
-
void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
synchronized (mControllerLock) {
mSessionInfo = info;
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index a73725b..1a41bc2 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -490,9 +490,11 @@
}
void MediaEvent::finalize() {
- if (mAvHandleRefCnt == 0 && mFilterClient != nullptr) {
- mFilterClient->releaseAvHandle(
- mAvHandle, mDataIdRefCnt == 0 ? mDataId : 0);
+ if (mAvHandleRefCnt == 0) {
+ if (mFilterClient != nullptr) {
+ mFilterClient->releaseAvHandle(
+ mAvHandle, mDataIdRefCnt == 0 ? mDataId : 0);
+ }
native_handle_close(mAvHandle);
}
}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index e4b9b5d..987b23f 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -1,9 +1,9 @@
LIBANDROID {
global:
- AActivityManager_addUidImportanceListener; # apex # introduced=31
- AActivityManager_removeUidImportanceListener; # apex # introduced=31
- AActivityManager_isUidActive; # apex # introduced=31
- AActivityManager_getUidImportance; # apex # introduced=31
+ AActivityManager_addUidImportanceListener; # systemapi # introduced=31
+ AActivityManager_removeUidImportanceListener; # systemapi # introduced=31
+ AActivityManager_isUidActive; # systemapi # introduced=31
+ AActivityManager_getUidImportance; # systemapi # introduced=31
AAssetDir_close;
AAssetDir_getNextFileName;
AAssetDir_rewind;
diff --git a/omapi/OWNERS b/omapi/OWNERS
index 5682fd3..1dce1e0 100644
--- a/omapi/OWNERS
+++ b/omapi/OWNERS
@@ -1,5 +1,6 @@
# Bug component: 456592
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
jackcwyu@google.com
diff --git a/omapi/java/android/se/OWNERS b/omapi/java/android/se/OWNERS
index 5682fd3..1dce1e0 100644
--- a/omapi/java/android/se/OWNERS
+++ b/omapi/java/android/se/OWNERS
@@ -1,5 +1,6 @@
# Bug component: 456592
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
jackcwyu@google.com
diff --git a/omapi/java/android/se/omapi/OWNERS b/omapi/java/android/se/omapi/OWNERS
index 5682fd3..1dce1e0 100644
--- a/omapi/java/android/se/omapi/OWNERS
+++ b/omapi/java/android/se/omapi/OWNERS
@@ -1,5 +1,6 @@
# Bug component: 456592
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
jackcwyu@google.com
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 3f86aba..8f9730a 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,8 +34,7 @@
android:label="@string/app_name"
android:directBootAware="true"
android:usesCleartextTraffic="true"
- android:icon="@mipmap/ic_launcher_android"
- android:debuggable="true">
+ android:icon="@mipmap/ic_launcher_android">
<receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver"
android:exported="true">
<intent-filter>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 81505e1..d6909719 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -11,13 +11,34 @@
<string name="string_continue">Continue</string>
<!-- This is a label for a button that links to different places where the user can save their passkeys. [CHAR LIMIT=20] -->
<string name="string_more_options">More options</string>
+ <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
+ <string name="string_learn_more">Learn more</string>
<!-- This string introduces passkeys to the users for the first time they use this method. Tip: to avoid gendered language patterns, this header could be translated as if the original string were "More safety with passkeys". [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_title">Safer with passkeys</string>
- <!-- These strings highlight passkey benefits. [CHAR LIMIT=200] -->
+ <!-- This string highlight passkey benefits related with the password. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_body_password">With passkeys, you don’t need to create or remember complex passwords</string>
+ <!-- This string highlight passkey benefits related with encrypted. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_body_fingerprint">Passkeys are encrypted digital keys you create using your fingerprint, face, or screen lock</string>
+ <!-- This string highlight passkey benefits related with signing with other devices. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_body_device">They are saved to a password manager, so you can sign in on other devices</string>
-
+ <!-- This string introduces passkeys in more detail to the users for the first time they use this method. [CHAR LIMIT=200] -->
+ <string name="more_about_passkeys_title">More about passkeys</string>
+ <!-- Title for subsection of "Learn more about passkeys" screen about passwordless technology. [CHAR LIMIT=80] -->
+ <string name="passwordless_technology_title">Passwordless technology</string>
+ <!-- Detail for subsection of "Learn more about passkeys" screen about passwordless technology. [CHAR LIMIT=500] -->
+ <string name="passwordless_technology_detail">Passkeys allow you to sign in without relying on passwords. You just need to use your fingerprint, face recognition, PIN, or swipe pattern to verify your identity and create a passkey.</string>
+ <!-- Title for subsection of "Learn more about passkeys" screen about public key cryptography. [CHAR LIMIT=80] -->
+ <string name="public_key_cryptography_title">Public key cryptography</string>
+ <!-- Detail for subsection of "Learn more about passkeys" screen about public key cryptography. [CHAR LIMIT=500] -->
+ <string name="public_key_cryptography_detail">Based on FIDO Alliance (which includes Google, Apple, Microsoft, and more) and W3C standards, passkeys use cryptographic key pairs. Unlike the username and string of characters we use for passwords, a private-public key pair is created for an app or website. The private key is safely stored on your device or password manager and it confirms your identity. The public key is shared with the app or website server. With corresponding keys, you can instantly register and sign in.</string>
+ <!-- Title for subsection of "Learn more about passkeys" screen about improved account security. [CHAR LIMIT=80] -->
+ <string name="improved_account_security_title">Improved account security</string>
+ <!-- Detail for subsection of "Learn more about passkeys" screen about improved account security. [CHAR LIMIT=500] -->
+ <string name="improved_account_security_detail">Each key is exclusively linked with the app or website they were created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder.</string>
+ <!-- Title for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=80] -->
+ <string name="seamless_transition_title">Seamless transition</string>
+ <!-- Detail for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=500] -->
+ <string name="seamless_transition_detail">As we move towards a passwordless future, passwords will still be available alongside passkeys.</string>
<!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
<string name="choose_provider_title">Choose where to save your <xliff:g id="createTypes" example="passkeys">%1$s</xliff:g></string>
<!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 7d43364..0761b64 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -99,10 +99,20 @@
)
}
- fun onCancel() {
+ // The dialog is canceled by the user.
+ fun onUserCancel() {
+ onCancel(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
+ }
+
+ // The dialog is canceled because we launched into settings.
+ fun onSettingLaunchCancel() {
+ onCancel(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION)
+ }
+
+ private fun onCancel(cancelCode: Int) {
val resultData = Bundle()
BaseDialogResult.addToBundle(BaseDialogResult(requestInfo.token), resultData)
- resultReceiver?.send(BaseDialogResult.RESULT_CODE_DIALOG_CANCELED, resultData)
+ resultReceiver?.send(cancelCode, resultData)
}
fun onOptionSelected(
@@ -414,7 +424,7 @@
credentialData,
// TODO: populate with actual data
/*candidateQueryData=*/ Bundle(),
- /*requireSystemProvider=*/ false
+ /*isSystemProviderRequired=*/ false
),
"com.google.android.youtube"
)
@@ -429,7 +439,7 @@
data,
// TODO: populate with actual data
/*candidateQueryData=*/ Bundle(),
- /*requireSystemProvider=*/ false
+ /*isSystemProviderRequired=*/ false
),
"com.google.android.youtube"
)
@@ -443,7 +453,7 @@
"other-sign-ins",
data,
/*candidateQueryData=*/ Bundle(),
- /*requireSystemProvider=*/ false
+ /*isSystemProviderRequired=*/ false
),
"com.google.android.youtube"
)
@@ -457,7 +467,7 @@
)
.addGetCredentialOption(
GetCredentialOption(
- TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*requireSystemProvider=*/ false)
+ TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*isSystemProviderRequired=*/ false)
)
.build(),
"com.google.android.youtube"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 09f9b5e..d9e4dc8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -269,7 +269,7 @@
val createCredentialRequest = requestInfo.createCredentialRequest
val createCredentialRequestJetpack = createCredentialRequest?.let {
CreateCredentialRequest.createFrom(
- it.type, it.credentialData, it.candidateQueryData, it.requireSystemProvider()
+ it.type, it.credentialData, it.candidateQueryData, it.isSystemProviderRequired()
)
}
when (createCredentialRequestJetpack) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 498f0a1..1e1c833 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -70,7 +70,7 @@
when (uiState.currentScreenState) {
CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
onConfirm = viewModel::onConfirmIntro,
- onCancel = viewModel::onCancel,
+ onLearnMore = viewModel::onLearnMore,
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
@@ -115,7 +115,10 @@
activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
onOptionSelected = viewModel::onEntrySelected,
onConfirm = viewModel::onConfirmEntrySelected,
- onCancel = viewModel::onCancel,
+ )
+ CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
+ onBackPasskeyIntroButtonSelected =
+ viewModel::onBackPasskeyIntroButtonSelected,
)
}
} else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
@@ -136,7 +139,7 @@
@Composable
fun ConfirmationCard(
onConfirm: () -> Unit,
- onCancel: () -> Unit,
+ onLearnMore: () -> Unit,
) {
ContainerCard() {
Column() {
@@ -223,8 +226,8 @@
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
ActionButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
+ stringResource(R.string.string_learn_more),
+ onClick = onLearnMore
)
ConfirmButton(
stringResource(R.string.string_continue),
@@ -396,8 +399,7 @@
)
}
},
- colors = TopAppBarDefaults.smallTopAppBarColors
- (containerColor = Color.Transparent),
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
modifier = Modifier.padding(top = 12.dp)
)
Divider(
@@ -475,7 +477,8 @@
Icons.Outlined.NewReleases,
contentDescription = null,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(all = 24.dp)
+ .padding(all = 24.dp),
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
)
TextOnSurface(
text = stringResource(
@@ -634,14 +637,13 @@
activeRemoteEntry: EntryInfo,
onOptionSelected: (EntryInfo) -> Unit,
onConfirm: () -> Unit,
- onCancel: () -> Unit,
) {
ContainerCard() {
Column() {
Icon(
painter = painterResource(R.drawable.ic_other_devices),
contentDescription = null,
- tint = Color.Unspecified,
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
.padding(all = 24.dp).size(32.dp)
)
@@ -673,13 +675,9 @@
color = Color.Transparent
)
Row(
- horizontalArrangement = Arrangement.SpaceBetween,
+ horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- ActionButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
- )
ConfirmButton(
stringResource(R.string.string_continue),
onClick = onConfirm
@@ -696,6 +694,92 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
+fun MoreAboutPasskeysIntroCard(
+ onBackPasskeyIntroButtonSelected: () -> Unit,
+) {
+ ContainerCard() {
+ Column() {
+ TopAppBar(
+ title = {
+ TextOnSurface(
+ text =
+ stringResource(
+ R.string.more_about_passkeys_title),
+ style = MaterialTheme.typography.titleMedium,
+ )
+ },
+ navigationIcon = {
+ IconButton(
+ onClick = onBackPasskeyIntroButtonSelected
+ ) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ stringResource(R.string.accessibility_back_arrow_button)
+ )
+ }
+ },
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
+ modifier = Modifier.padding(top = 12.dp)
+ )
+ Column(
+ modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 68.dp)
+ ) {
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.passwordless_technology_title),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ TextSecondary(
+ text = stringResource(R.string.passwordless_technology_detail),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.public_key_cryptography_title),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ TextSecondary(
+ text = stringResource(R.string.public_key_cryptography_detail),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.improved_account_security_title),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ TextSecondary(
+ text = stringResource(R.string.improved_account_security_detail),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.seamless_transition_title),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ TextSecondary(
+ text = stringResource(R.string.seamless_transition_detail),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 24.dp)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
entryInfo: EntryInfo,
@@ -889,7 +973,8 @@
Icon(
Icons.Filled.Add,
contentDescription = null,
- modifier = Modifier.padding(start = 16.dp)
+ modifier = Modifier.padding(start = 16.dp),
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
)
},
label = {
@@ -923,8 +1008,8 @@
Icon(
painter = painterResource(R.drawable.ic_other_devices),
contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.padding(start = 18.dp)
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ modifier = Modifier.padding(start = 10.dp)
)
},
label = {
@@ -932,7 +1017,7 @@
TextOnSurfaceVariant(
text = stringResource(R.string.another_device),
style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
+ modifier = Modifier.padding(start = 10.dp, top = 18.dp, bottom = 18.dp)
.align(alignment = Alignment.CenterHorizontally),
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index ac84503..a9b1d332 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -122,6 +122,12 @@
)
}
+ fun onBackPasskeyIntroButtonSelected() {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.PASSKEY_INTRO,
+ )
+ }
+
fun onEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
uiState = uiState.copy(
currentScreenState = if (
@@ -141,15 +147,21 @@
}
fun onDisabledProvidersSelected() {
- credManRepo.onCancel()
+ credManRepo.onSettingLaunchCancel()
dialogResult.tryEmit(DialogResult(ResultState.LAUNCH_SETTING_CANCELED))
}
fun onCancel() {
- credManRepo.onCancel()
+ credManRepo.onUserCancel()
dialogResult.tryEmit(DialogResult(ResultState.NORMAL_CANCELED))
}
+ fun onLearnMore() {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO,
+ )
+ }
+
fun onChangeDefaultSelected() {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 97477a7..957488f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -91,6 +91,7 @@
/** The name of the current screen. */
enum class CreateScreenState {
PASSKEY_INTRO,
+ MORE_ABOUT_PASSKEYS_INTRO,
PROVIDER_SELECTION,
CREATION_OPTION_SELECTION,
MORE_OPTIONS_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 1d84cf9..420b4d5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -63,6 +63,7 @@
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.common.ui.ActionButton
+import com.android.credentialmanager.common.ui.ConfirmButton
import com.android.credentialmanager.common.ui.Entry
import com.android.credentialmanager.common.ui.TextOnSurface
import com.android.credentialmanager.common.ui.TextSecondary
@@ -95,7 +96,10 @@
PrimarySelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerDisplayInfo = uiState.providerDisplayInfo,
+ providerInfoList = uiState.providerInfoList,
+ activeEntry = uiState.activeEntry,
onEntrySelected = viewModel::onEntrySelected,
+ onConfirm = viewModel::onConfirmEntrySelected,
onMoreOptionSelected = viewModel::onMoreOptionSelected,
)
} else {
@@ -133,7 +137,10 @@
fun PrimarySelectionCard(
requestDisplayInfo: RequestDisplayInfo,
providerDisplayInfo: ProviderDisplayInfo,
+ providerInfoList: List<ProviderInfo>,
+ activeEntry: EntryInfo?,
onEntrySelected: (EntryInfo) -> Unit,
+ onConfirm: () -> Unit,
onMoreOptionSelected: () -> Unit,
) {
val sortedUserNameToCredentialEntryList =
@@ -217,13 +224,33 @@
thickness = 24.dp,
color = Color.Transparent
)
+ var totalEntriesCount = sortedUserNameToCredentialEntryList
+ .flatMap{ it.sortedCredentialEntryList}.size + authenticationEntryList
+ .size + providerInfoList.flatMap { it.actionEntryList }.size
+ if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1
+ // Row horizontalArrangement differs on only one actionButton(should place on most
+ // left)/only one confirmButton(should place on most right)/two buttons exist the same
+ // time(should be one on the left, one on the right)
Row(
- horizontalArrangement = Arrangement.SpaceBetween,
+ horizontalArrangement =
+ if (totalEntriesCount <= 1 && activeEntry != null) Arrangement.End
+ else if (totalEntriesCount > 1 && activeEntry == null) Arrangement.Start
+ else Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- ActionButton(
- stringResource(R.string.get_dialog_use_saved_passkey_for),
- onMoreOptionSelected)
+ if (totalEntriesCount > 1) {
+ ActionButton(
+ stringResource(R.string.get_dialog_use_saved_passkey_for),
+ onMoreOptionSelected
+ )
+ }
+ // Only one sign-in options exist
+ if (activeEntry != null) {
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
}
Divider(
thickness = 18.dp,
@@ -251,7 +278,7 @@
ContainerCard() {
Column() {
TopAppBar(
- colors = TopAppBarDefaults.smallTopAppBarColors(
+ colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
title = {
@@ -388,8 +415,8 @@
Icon(
painter = painterResource(R.drawable.ic_other_devices),
contentDescription = null,
- tint = Color.Unspecified,
- modifier = Modifier.padding(start = 16.dp)
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ modifier = Modifier.padding(start = 10.dp)
)
},
label = {
@@ -397,7 +424,7 @@
text = stringResource(
R.string.get_dialog_option_headline_use_a_different_device),
style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp)
+ modifier = Modifier.padding(start = 10.dp, top = 18.dp, bottom = 18.dp)
.align(alignment = Alignment.CenterHorizontally)
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 6f0f76b..c72ae72 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -41,6 +41,7 @@
val currentScreenState: GetScreenState = toGetScreenState(providerInfoList),
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
val selectedEntry: EntryInfo? = null,
+ val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo),
val hidden: Boolean = false,
val providerActivityPending: Boolean = false,
val isNoAccount: Boolean = false,
@@ -73,6 +74,17 @@
}
}
+ fun onConfirmEntrySelected() {
+ val activeEntry = uiState.activeEntry
+ if (activeEntry != null) {
+ onEntrySelected(activeEntry)
+ } else {
+ Log.w("Account Selector",
+ "Illegal state: confirm is pressed but activeEntry isn't set.")
+ dialogResult.tryEmit(DialogResult(ResultState.COMPLETE))
+ }
+ }
+
fun launchProviderUi(
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
@@ -140,7 +152,7 @@
}
fun onCancel() {
- credManRepo.onCancel()
+ credManRepo.onUserCancel()
dialogResult.tryEmit(DialogResult(ResultState.NORMAL_CANCELED))
}
}
@@ -198,6 +210,26 @@
)
}
+private fun toActiveEntry(
+ providerDisplayInfo: ProviderDisplayInfo,
+): EntryInfo? {
+ val sortedUserNameToCredentialEntryList =
+ providerDisplayInfo.sortedUserNameToCredentialEntryList
+ val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+ var activeEntry: EntryInfo? = null
+ if (sortedUserNameToCredentialEntryList
+ .size == 1 && authenticationEntryList.isEmpty()
+ ) {
+ activeEntry = sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList.first()
+ } else if (
+ sortedUserNameToCredentialEntryList
+ .isEmpty() && authenticationEntryList.size == 1
+ ) {
+ activeEntry = authenticationEntryList.first()
+ }
+ return activeEntry
+}
+
private fun toGetScreenState(
providerInfoList: List<ProviderInfo>
): GetScreenState {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
index 18d5089..5cb8d3b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
@@ -66,7 +66,7 @@
it.type,
it.credentialRetrievalData,
it.candidateQueryData,
- it.requireSystemProvider()
+ it.isSystemProviderRequired()
)
}
)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 21f4be0..1bbdad5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -275,8 +275,11 @@
}
/**
- * Returns whether there is only one user on this device, not including
- * the system-only user.
+ * Returns whether there is only one "full" user on this device.
+ *
+ * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode()
+ * headless system user mode}, the system user is not "full", so it's not be considered in the
+ * calculation.
*/
private boolean isSingleUser(UserManager userManager) {
final int userCount = userManager.getUserCount();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 5c5720a..cc2e3a6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -128,8 +128,7 @@
}
/**
- * Returns whether there is only one user on this device, not including
- * the system-only user.
+ * Returns whether there is only one user on this device.
*/
private boolean isSingleUser(UserManager userManager) {
final int userCount = userManager.getUserCount();
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index eb2bffe..1aa2079 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -25,7 +25,7 @@
import androidx.navigation.NavHostController
interface NavControllerWrapper {
- fun navigate(route: String)
+ fun navigate(route: String, popUpCurrent: Boolean = false)
fun navigateBack()
val highlightEntryId: String?
@@ -48,17 +48,17 @@
val LocalNavController = compositionLocalOf<NavControllerWrapper> {
object : NavControllerWrapper {
- override fun navigate(route: String) {}
+ override fun navigate(route: String, popUpCurrent: Boolean) {}
override fun navigateBack() {}
}
}
@Composable
-fun navigator(route: String?): () -> Unit {
+fun navigator(route: String?, popUpCurrent: Boolean = false): () -> Unit {
if (route == null) return {}
val navController = LocalNavController.current
- return { navController.navigate(route) }
+ return { navController.navigate(route, popUpCurrent) }
}
internal class NavControllerWrapperImpl(
@@ -68,8 +68,16 @@
var highlightId: String? = null
var sessionName: String? = null
- override fun navigate(route: String) {
- navController.navigate(route)
+ override fun navigate(route: String, popUpCurrent: Boolean) {
+ navController.navigate(route) {
+ if (popUpCurrent) {
+ navController.currentDestination?.let { currentDestination ->
+ popUpTo(currentDestination.id) {
+ inclusive = true
+ }
+ }
+ }
+ }
}
override fun navigateBack() {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/NavControllerWrapperTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/NavControllerWrapperTest.kt
new file mode 100644
index 0000000..118585e
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/NavControllerWrapperTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.settingslib.spa.framework.compose
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.waitUntilExists
+import kotlinx.coroutines.delay
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NavControllerWrapperTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun navigate_canNavigate() {
+ composeTestRule.setContent {
+ TestNavHost {
+ LocalNavController.current.navigate(ROUTE_B)
+ }
+ }
+
+ composeTestRule.onNodeWithText(ROUTE_B).assertIsDisplayed()
+ }
+
+ @Test
+ fun navigate_canNavigateBack() {
+ composeTestRule.setContent {
+ TestNavHost {
+ val navController = LocalNavController.current
+ LaunchedEffect(Unit) {
+ navController.navigate(ROUTE_B)
+ delay(100)
+ navController.navigateBack()
+ }
+ }
+ }
+
+ composeTestRule.waitUntilExists(hasText(ROUTE_A))
+ }
+
+ @Test
+ fun navigate_canNavigateAndPopUpCurrent() {
+ composeTestRule.setContent {
+ TestNavHost {
+ val navController = LocalNavController.current
+ LaunchedEffect(Unit) {
+ navController.navigate(ROUTE_B)
+ delay(100)
+ navController.navigate(ROUTE_C, popUpCurrent = true)
+ delay(100)
+ navController.navigateBack()
+ }
+ }
+ }
+
+ composeTestRule.waitUntilExists(hasText(ROUTE_A))
+ }
+
+ private companion object {
+ @Composable
+ fun TestNavHost(content: @Composable () -> Unit) {
+ val navController = rememberNavController()
+ CompositionLocalProvider(navController.localNavController()) {
+ NavHost(navController, ROUTE_A) {
+ composable(route = ROUTE_A) { Text(ROUTE_A) }
+ composable(route = ROUTE_B) { Text(ROUTE_B) }
+ composable(route = ROUTE_C) { Text(ROUTE_C) }
+ }
+ content()
+ }
+ }
+
+ const val ROUTE_A = "RouteA"
+ const val ROUTE_B = "RouteB"
+ const val ROUTE_C = "RouteC"
+ }
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt
index 5a3044d..f289d0d 100644
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt
@@ -25,7 +25,7 @@
var navigateCalledWith: String? = null
var navigateBackIsCalled = false
- override fun navigate(route: String) {
+ override fun navigate(route: String, popUpCurrent: Boolean) {
navigateCalledWith = route
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
index a2fb101..3b9bf47 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
@@ -33,7 +33,6 @@
fun DisposableBroadcastReceiverAsUser(
intentFilter: IntentFilter,
userHandle: UserHandle,
- onStart: () -> Unit = {},
onReceive: (Intent) -> Unit,
) {
val context = LocalContext.current
@@ -49,7 +48,6 @@
context.registerReceiverAsUser(
broadcastReceiver, userHandle, intentFilter, null, null
)
- onStart()
},
onStop = {
context.unregisterReceiver(broadcastReceiver)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index ce0c551..5342def 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -29,19 +29,11 @@
import kotlinx.coroutines.runBlocking
/**
- * The config used to load the App List.
- */
-data class AppListConfig(
- val userId: Int,
- val showInstantApps: Boolean,
-)
-
-/**
* The repository to load the App List data.
*/
internal interface AppListRepository {
/** Loads the list of [ApplicationInfo]. */
- suspend fun loadApps(config: AppListConfig): List<ApplicationInfo>
+ suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo>
/** Gets the flow of predicate that could used to filter system app. */
fun showSystemPredicate(
@@ -50,7 +42,7 @@
): Flow<(app: ApplicationInfo) -> Boolean>
/** Gets the system app package names. */
- fun getSystemPackageNamesBlocking(config: AppListConfig): Set<String>
+ fun getSystemPackageNamesBlocking(userId: Int, showInstantApps: Boolean): Set<String>
}
/**
@@ -59,15 +51,21 @@
object AppListRepositoryUtil {
/** Gets the system app package names. */
@JvmStatic
- fun getSystemPackageNames(context: Context, config: AppListConfig): Set<String> {
- return AppListRepositoryImpl(context).getSystemPackageNamesBlocking(config)
- }
+ fun getSystemPackageNames(
+ context: Context,
+ userId: Int,
+ showInstantApps: Boolean,
+ ): Set<String> =
+ AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId, showInstantApps)
}
internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
private val packageManager = context.packageManager
- override suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> = coroutineScope {
+ override suspend fun loadApps(
+ userId: Int,
+ showInstantApps: Boolean,
+ ): List<ApplicationInfo> = coroutineScope {
val hiddenSystemModulesDeferred = async {
packageManager.getInstalledModules(0)
.filter { it.isHidden }
@@ -82,12 +80,12 @@
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
)
val installedApplicationsAsUser =
- packageManager.getInstalledApplicationsAsUser(flags, config.userId)
+ packageManager.getInstalledApplicationsAsUser(flags, userId)
val hiddenSystemModules = hiddenSystemModulesDeferred.await()
val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
installedApplicationsAsUser.filter { app ->
- app.isInAppList(config.showInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
+ app.isInAppList(showInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
}
}
@@ -97,18 +95,17 @@
): Flow<(app: ApplicationInfo) -> Boolean> =
userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
- override fun getSystemPackageNamesBlocking(config: AppListConfig) = runBlocking {
- getSystemPackageNames(config)
- }
+ override fun getSystemPackageNamesBlocking(userId: Int, showInstantApps: Boolean) =
+ runBlocking { getSystemPackageNames(userId, showInstantApps) }
- private suspend fun getSystemPackageNames(config: AppListConfig): Set<String> =
- coroutineScope {
- val loadAppsDeferred = async { loadApps(config) }
- val homeOrLauncherPackages = loadHomeOrLauncherPackages(config.userId)
- val showSystemPredicate =
- { app: ApplicationInfo -> isSystemApp(app, homeOrLauncherPackages) }
- loadAppsDeferred.await().filter(showSystemPredicate).map { it.packageName }.toSet()
- }
+ private suspend fun getSystemPackageNames(userId: Int, showInstantApps: Boolean): Set<String> =
+ coroutineScope {
+ val loadAppsDeferred = async { loadApps(userId, showInstantApps) }
+ val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
+ val showSystemPredicate =
+ { app: ApplicationInfo -> isSystemApp(app, homeOrLauncherPackages) }
+ loadAppsDeferred.await().filter(showSystemPredicate).map { it.packageName }.toSet()
+ }
private suspend fun showSystemPredicate(
userId: Int,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 6cd1c0d..8896042 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spa.framework.util.waitFirst
import com.android.settingslib.spa.widget.ui.SpinnerOption
+import com.android.settingslib.spaprivileged.template.app.AppListConfig
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,8 +35,8 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
@@ -78,32 +79,77 @@
private val labelMap = ConcurrentHashMap<String, String>()
private val scope = viewModelScope + Dispatchers.IO
- private val userIdFlow = appListConfig.flow.map { it.userId }
+ private val userSubGraphsFlow = appListConfig.flow.map { config ->
+ config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) }
+ }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
- private val appsStateFlow = MutableStateFlow<List<ApplicationInfo>?>(null)
+ private inner class UserSubGraph(
+ private val userId: Int,
+ private val showInstantApps: Boolean,
+ ) {
+ private val userIdFlow = flowOf(userId)
- private val recordListFlow = listModel.flow
- .flatMapLatest { it.transform(userIdFlow, appsStateFlow.filterNotNull()) }
- .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
+ private val appsStateFlow = MutableStateFlow<List<ApplicationInfo>?>(null)
- private val systemFilteredFlow =
- appListRepository.showSystemPredicate(userIdFlow, showSystem.flow)
- .combine(recordListFlow) { showAppPredicate, recordList ->
- recordList.filter { showAppPredicate(it.app) }
+ val recordListFlow = listModel.flow
+ .flatMapLatest { it.transform(userIdFlow, appsStateFlow.filterNotNull()) }
+ .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
+
+ private val systemFilteredFlow =
+ appListRepository.showSystemPredicate(userIdFlow, showSystem.flow)
+ .combine(recordListFlow) { showAppPredicate, recordList ->
+ recordList.filter { showAppPredicate(it.app) }
+ }
+ .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
+
+ val listModelFilteredFlow = optionFlow.filterNotNull().flatMapLatest { option ->
+ listModel.flow.flatMapLatest { listModel ->
+ listModel.filter(this.userIdFlow, option, this.systemFilteredFlow)
}
+ }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
+
+ fun reloadApps() {
+ scope.launch {
+ appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps)
+ }
+ }
+ }
+
+ private val combinedRecordListFlow = userSubGraphsFlow.flatMapLatest { userSubGraphList ->
+ combine(userSubGraphList.map { it.recordListFlow }) { it.toList().flatten() }
+ }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
override val spinnerOptionsFlow =
- recordListFlow.combine(listModel.flow) { recordList, listModel ->
+ combinedRecordListFlow.combine(listModel.flow) { recordList, listModel ->
listModel.getSpinnerOptions(recordList)
- }
+ }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
- override val appListDataFlow = optionFlow.filterNotNull().flatMapLatest(::filterAndSort)
- .combine(searchQuery.flow) { appListData, searchQuery ->
+ private val appEntryListFlow = userSubGraphsFlow.flatMapLatest { userSubGraphList ->
+ combine(userSubGraphList.map { it.listModelFilteredFlow }) { it.toList().flatten() }
+ }.asyncMapItem { record ->
+ val label = getLabel(record.app)
+ AppEntry(
+ record = record,
+ label = label,
+ labelCollationKey = collator.getCollationKey(label),
+ )
+ }
+
+ override val appListDataFlow =
+ combine(
+ appEntryListFlow,
+ listModel.flow,
+ optionFlow.filterNotNull(),
+ ) { appEntries, listModel, option ->
+ AppListData(
+ appEntries = appEntries.sortedWith(listModel.getComparator(option)),
+ option = option,
+ )
+ }.combine(searchQuery.flow) { appListData, searchQuery ->
appListData.filter {
it.label.contains(other = searchQuery, ignoreCase = true)
}
- }
- .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
+ }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
init {
scheduleOnFirstLoaded()
@@ -111,30 +157,16 @@
fun reloadApps() {
scope.launch {
- appsStateFlow.value = appListRepository.loadApps(appListConfig.flow.first())
+ userSubGraphsFlow.collect { userSubGraphList ->
+ for (userSubGraph in userSubGraphList) {
+ userSubGraph.reloadApps()
+ }
+ }
}
}
- private fun filterAndSort(option: Int) = listModel.flow.flatMapLatest { listModel ->
- listModel.filter(userIdFlow, option, systemFilteredFlow)
- .asyncMapItem { record ->
- val label = getLabel(record.app)
- AppEntry(
- record = record,
- label = label,
- labelCollationKey = collator.getCollationKey(label),
- )
- }
- .map { appEntries ->
- AppListData(
- appEntries = appEntries.sortedWith(listModel.getComparator(option)),
- option = option,
- )
- }
- }
-
private fun scheduleOnFirstLoaded() {
- recordListFlow
+ combinedRecordListFlow
.waitFirst(appListDataFlow)
.combine(listModel.flow) { recordList, listModel ->
if (listModel.onFirstLoaded(recordList)) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 7199e3a..4a8c00e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -32,6 +32,7 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
@@ -43,18 +44,26 @@
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
import com.android.settingslib.spaprivileged.model.app.AppEntry
-import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppListViewModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
+import com.android.settingslib.spaprivileged.model.app.userId
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
private const val TAG = "AppList"
private const val CONTENT_TYPE_HEADER = "header"
+/**
+ * The config used to load the App List.
+ */
+data class AppListConfig(
+ val userIds: List<Int>,
+ val showInstantApps: Boolean,
+)
+
data class AppListState(
val showSystem: State<Boolean>,
val searchQuery: State<String>,
@@ -83,7 +92,7 @@
internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
viewModelSupplier: @Composable () -> IAppListViewModel<T>,
) {
- LogCompositions(TAG, config.userId.toString())
+ LogCompositions(TAG, config.userIds.toString())
val viewModel = viewModelSupplier()
Column(Modifier.fillMaxSize()) {
val optionsState = viewModel.spinnerOptionsFlow.collectAsState(null, Dispatchers.IO)
@@ -135,7 +144,7 @@
header()
}
- items(count = list.size, key = { option to list[it].record.app.packageName }) {
+ items(count = list.size, key = { list[it].record.itemKey(option) }) {
remember(list) { getGroupTitleIfFirst(option, list, it) }
?.let { group -> CategoryTitle(title = group) }
@@ -149,6 +158,9 @@
}
}
+private fun <T : AppRecord> T.itemKey(option: Int) =
+ listOf(option, app.packageName, app.userId, System.identityHashCode(this))
+
/** Returns group title if this is the first item of the group. */
private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
option: Int,
@@ -164,21 +176,23 @@
listModel: AppListModel<T>,
state: AppListState,
): AppListViewModel<T> {
- val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
+ val viewModel: AppListViewModel<T> = viewModel(key = config.userIds.toString())
viewModel.appListConfig.setIfAbsent(config)
viewModel.listModel.setIfAbsent(listModel)
viewModel.showSystem.Sync(state.showSystem)
viewModel.searchQuery.Sync(state.searchQuery)
- DisposableBroadcastReceiverAsUser(
- intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
- addAction(Intent.ACTION_PACKAGE_REMOVED)
- addAction(Intent.ACTION_PACKAGE_CHANGED)
- addDataScheme("package")
- },
- userHandle = UserHandle.of(config.userId),
- onStart = { viewModel.reloadApps() },
- ) { viewModel.reloadApps() }
-
+ LifecycleEffect(onStart = { viewModel.reloadApps() })
+ val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
+ addAction(Intent.ACTION_PACKAGE_CHANGED)
+ addDataScheme("package")
+ }
+ for (userId in config.userIds) {
+ DisposableBroadcastReceiverAsUser(
+ intentFilter = intentFilter,
+ userHandle = UserHandle.of(userId),
+ ) { viewModel.reloadApps() }
+ }
return viewModel
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 404e27c..2ebbe8a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -24,10 +24,9 @@
import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
import com.android.settingslib.spa.widget.scaffold.SearchScaffold
import com.android.settingslib.spaprivileged.R
-import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
-import com.android.settingslib.spaprivileged.template.common.WorkProfilePager
+import com.android.settingslib.spaprivileged.template.common.UserProfilePager
/**
* The full screen template for an App List page.
@@ -55,10 +54,10 @@
}
},
) { bottomPadding, searchQuery ->
- WorkProfilePager(primaryUserOnly) { userInfo ->
+ UserProfilePager(primaryUserOnly) { userGroup ->
val appListInput = AppListInput(
config = AppListConfig(
- userId = userInfo.id,
+ userIds = userGroup.userInfos.map { it.id },
showInstantApps = showInstantApps,
),
listModel = listModel,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
new file mode 100644
index 0000000..b5a4929
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.settingslib.spaprivileged.template.common
+
+import android.content.pm.UserInfo
+import android.content.pm.UserProperties
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settingslib.spaprivileged.model.enterprise.EnterpriseRepository
+
+/**
+ * Info about how to group multiple profiles for Settings.
+ *
+ * @see [UserProperties.ShowInSettings]
+ */
+data class UserGroup(
+ /** The users in this user group, if multiple users, the first one is parent user. */
+ val userInfos: List<UserInfo>,
+)
+
+@Composable
+fun UserProfilePager(
+ primaryUserOnly: Boolean = false,
+ content: @Composable (userGroup: UserGroup) -> Unit,
+) {
+ val context = LocalContext.current
+ val userGroups = remember {
+ context.userManager.getUserGroups(primaryUserOnly)
+ }
+ val titles = remember {
+ val enterpriseRepository = EnterpriseRepository(context)
+ userGroups.map { userGroup ->
+ enterpriseRepository.getProfileTitle(
+ isManagedProfile = userGroup.userInfos.first().isManagedProfile,
+ )
+ }
+ }
+
+ SettingsPager(titles) { page ->
+ content(userGroups[page])
+ }
+}
+
+private fun UserManager.getUserGroups(primaryUserOnly: Boolean): List<UserGroup> {
+ val userGroupList = mutableListOf<UserGroup>()
+ val profileToShowInSettingsList = getProfiles(UserHandle.myUserId())
+ .filter { userInfo -> !primaryUserOnly || userInfo.isPrimary }
+ .map { userInfo -> userInfo to getUserProperties(userInfo.userHandle).showInSettings }
+
+ profileToShowInSettingsList.filter { it.second == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT }
+ .takeIf { it.isNotEmpty() }
+ ?.map { it.first }
+ ?.let { userInfos -> userGroupList += UserGroup(userInfos) }
+
+ profileToShowInSettingsList.filter { it.second == UserProperties.SHOW_IN_LAUNCHER_SEPARATE }
+ .forEach { userGroupList += UserGroup(userInfos = listOf(it.first)) }
+
+ return userGroupList
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
deleted file mode 100644
index a76c438..0000000
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
+++ /dev/null
@@ -1,50 +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.settingslib.spaprivileged.template.common
-
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import com.android.settingslib.spa.widget.scaffold.SettingsPager
-import com.android.settingslib.spaprivileged.model.enterprise.EnterpriseRepository
-
-@Composable
-fun WorkProfilePager(
- primaryUserOnly: Boolean = false,
- content: @Composable (userInfo: UserInfo) -> Unit,
-) {
- val context = LocalContext.current
- val profiles = remember {
- val userManager = checkNotNull(context.getSystemService(UserManager::class.java))
- userManager.getProfiles(UserHandle.myUserId()).filter { userInfo ->
- !primaryUserOnly || userInfo.isPrimary
- }
- }
- val titles = remember {
- val enterpriseRepository = EnterpriseRepository(context)
- profiles.map {
- enterpriseRepository.getProfileTitle(isManagedProfile = it.isManagedProfile)
- }
- }
-
- SettingsPager(titles) { page ->
- content(profiles[page])
- }
-}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
index 01f4cc6..9bd9242 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
@@ -85,23 +85,6 @@
assertThat(onReceiveIsCalled).isTrue()
}
- @Test
- fun broadcastReceiver_onStartIsCalled() {
- var onStartIsCalled = false
- composeTestRule.setContent {
- CompositionLocalProvider(LocalContext provides context) {
- DisposableBroadcastReceiverAsUser(
- intentFilter = IntentFilter(),
- userHandle = USER_HANDLE,
- onStart = { onStartIsCalled = true },
- onReceive = {},
- )
- }
- }
-
- assertThat(onStartIsCalled).isTrue()
- }
-
private companion object {
val USER_HANDLE: UserHandle = UserHandle.of(0)
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index b0ea40a..57972ed 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -83,9 +83,8 @@
@Test
fun loadApps_notShowInstantApps() = runTest {
mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
- val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
- val appListFlow = repository.loadApps(appListConfig)
+ val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).containsExactly(NORMAL_APP)
}
@@ -93,9 +92,8 @@
@Test
fun loadApps_showInstantApps() = runTest {
mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
- val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
- val appListFlow = repository.loadApps(appListConfig)
+ val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true)
assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
}
@@ -109,9 +107,8 @@
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
mockInstalledApplications(listOf(app))
- val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
- val appListFlow = repository.loadApps(appListConfig)
+ val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).isEmpty()
}
@@ -126,9 +123,8 @@
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
mockInstalledApplications(listOf(app))
- val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
- val appListFlow = repository.loadApps(appListConfig)
+ val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).isEmpty()
}
@@ -142,9 +138,8 @@
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
mockInstalledApplications(listOf(app))
- val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
- val appListFlow = repository.loadApps(appListConfig)
+ val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).containsExactly(app)
}
@@ -157,9 +152,8 @@
enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
}
mockInstalledApplications(listOf(app))
- val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
- val appListFlow = repository.loadApps(appListConfig)
+ val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).containsExactly(app)
}
@@ -171,9 +165,8 @@
enabled = false
}
mockInstalledApplications(listOf(app))
- val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
- val appListFlow = repository.loadApps(appListConfig)
+ val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
assertThat(appListFlow).isEmpty()
}
@@ -223,7 +216,7 @@
@Test
fun showSystemPredicate_appInLauncher() = runTest {
- val app = IN_LAUMCHER_APP
+ val app = IN_LAUNCHER_APP
whenever(
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
@@ -237,10 +230,13 @@
@Test
fun getSystemPackageNames_returnExpectedValues() = runTest {
mockInstalledApplications(listOf(
- NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUMCHER_APP))
- val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+ NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP))
- val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(context, appListConfig)
+ val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
+ context = context,
+ userId = USER_ID,
+ showInstantApps = false,
+ )
assertThat(systemPackageNames).containsExactly("system.app", "home.app", "app.in.launcher")
}
@@ -280,7 +276,7 @@
flags = ApplicationInfo.FLAG_SYSTEM
}
- val IN_LAUMCHER_APP = ApplicationInfo().apply {
+ val IN_LAUNCHER_APP = ApplicationInfo().apply {
packageName = "app.in.launcher"
flags = ApplicationInfo.FLAG_SYSTEM
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index b1d02f5..fc40aed 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.mapItem
import com.android.settingslib.spa.testutils.waitUntil
+import com.android.settingslib.spaprivileged.template.app.AppListConfig
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -84,14 +85,17 @@
}
private object FakeAppListRepository : AppListRepository {
- override suspend fun loadApps(config: AppListConfig) = listOf(APP)
+ override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP)
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
- override fun getSystemPackageNamesBlocking(config: AppListConfig): Set<String> = setOf()
+ override fun getSystemPackageNamesBlocking(
+ userId: Int,
+ showInstantApps: Boolean,
+ ): Set<String> = emptySet()
}
private object FakeAppRepository : AppRepository {
@@ -105,7 +109,7 @@
const val USER_ID = 0
const val PACKAGE_NAME = "package.name"
const val LABEL = "Label"
- val CONFIG = AppListConfig(userId = USER_ID, showInstantApps = false)
+ val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false)
val APP = ApplicationInfo().apply {
packageName = PACKAGE_NAME
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index d5c7c19..a99d02d 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -32,7 +32,6 @@
import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppEntry
-import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
@@ -115,7 +114,7 @@
) {
composeTestRule.setContent {
AppListInput(
- config = AppListConfig(userId = USER_ID, showInstantApps = false),
+ config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false),
listModel = TestAppListModel(enableGrouping = enableGrouping),
state = AppListState(
showSystem = false.toState(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index db224be..a82f070 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -30,6 +30,8 @@
public static final String CATEGORY_APPS = "com.android.settings.category.ia.apps";
public static final String CATEGORY_APPS_DEFAULT =
"com.android.settings.category.ia.apps.default";
+ public static final String CATEGORY_SPECIAL_APP_ACCESS =
+ "com.android.settings.category.ia.special_app_access";
public static final String CATEGORY_BATTERY = "com.android.settings.category.ia.battery";
public static final String CATEGORY_DISPLAY = "com.android.settings.category.ia.display";
public static final String CATEGORY_SOUND = "com.android.settings.category.ia.sound";
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index a1a9e8c..7b8ca4d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -306,6 +306,13 @@
VALIDATORS.put(Global.Wearable.COMPANION_OS_VERSION, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.Wearable.ENABLE_ALL_LANGUAGES, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.OEM_SETUP_VERSION, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(
+ Global.Wearable.OEM_SETUP_COMPLETED_STATUS,
+ new DiscreteValueValidator(
+ new String[] {
+ String.valueOf(Global.Wearable.OEM_SETUP_COMPLETED_FAILURE),
+ String.valueOf(Global.Wearable.OEM_SETUP_COMPLETED_SUCCESS),
+ }));
VALIDATORS.put(Global.Wearable.MASTER_GESTURES_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.UNGAZE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5ee36f3..db7032e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -645,6 +645,7 @@
Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
Settings.Global.Wearable.SETUP_LOCALE,
Settings.Global.Wearable.OEM_SETUP_VERSION,
+ Settings.Global.Wearable.OEM_SETUP_COMPLETED_STATUS,
Settings.Global.Wearable.MASTER_GESTURES_ENABLED,
Settings.Global.Wearable.UNGAZE_ENABLED,
Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c641a85..b4598bf 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -646,6 +646,9 @@
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+ <!-- Permission required for CTS test - SoundDoseHelperTest -->
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS" />
+
<!-- Permission required for CTS test - CtsAmbientContextDetectionServiceDeviceTest -->
<uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 697e181..f305981 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -112,6 +112,24 @@
],
}
+//Create a library to expose SystemUI's resources to other modules.
+android_library {
+ name: "SystemUI-res",
+ resource_dirs: [
+ "res-product",
+ "res-keyguard",
+ "res",
+ ],
+ static_libs: [
+ "SystemUISharedLib",
+ "SettingsLib",
+ "androidx.leanback_leanback",
+ "androidx.slice_slice-core",
+ "androidx.slice_slice-view",
+ ],
+ manifest: "AndroidManifest-res.xml",
+}
+
android_library {
name: "SystemUI-core",
defaults: [
diff --git a/packages/SystemUI/AndroidManifest-res.xml b/packages/SystemUI/AndroidManifest-res.xml
new file mode 100644
index 0000000..58e9dc6
--- /dev/null
+++ b/packages/SystemUI/AndroidManifest-res.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest package= "com.android.systemui.res">
+ <application/>
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 26a68bc..628fb38 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -937,7 +937,7 @@
android:excludeFromRecents="true"
android:theme="@android:style/Theme.NoDisplay"
android:label="@string/note_task_button_label"
- android:icon="@drawable/ic_note_task_button">
+ android:icon="@drawable/ic_note_task_shortcut_widget">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index ee8d023..2910bba 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -5,46 +5,72 @@
SystemUI is a persistent process that provides UI for the system but outside
of the system_server process.
-The starting point for most of sysui code is a list of services that extend
-SystemUI that are started up by SystemUIApplication. These services then depend
-on some custom dependency injection provided by Dependency.
-
Inputs directed at sysui (as opposed to general listeners) generally come in
through IStatusBar. Outputs from sysui are through a variety of private APIs to
the android platform all over.
## SystemUIApplication
-When SystemUIApplication starts up, it will start up the services listed in
-config_systemUIServiceComponents or config_systemUIServiceComponentsPerUser.
+When SystemUIApplication starts up, it instantiates a Dagger graph from which
+various pieces of the application are built.
-Each of these services extend SystemUI. SystemUI provides them with a Context
-and gives them callbacks for onConfigurationChanged (this historically was
-the main path for onConfigurationChanged, now also happens through
-ConfigurationController). They also receive a callback for onBootCompleted
+To support customization, SystemUIApplication relies on the AndroidManifest.xml
+having an `android.app.AppComponentFactory` specified. Specifically, it relies
+on an `AppComponentFactory` that subclases `SystemUIAppComponentFactoryBase`.
+Implementations of this abstract base class must override
+`#createSystemUIInitializer(Context)` which returns a `SystemUIInitializer`.
+`SystemUIInitializer` primary job in turn is to intialize and return the Dagger
+root component back to the `SystemUIApplication`.
+
+Writing a custom `SystemUIAppComponentFactoryBase` and `SystemUIInitializer`,
+should be enough for most implementations to stand up a customized Dagger
+graph, and launch a custom version of SystemUI.
+
+## Dagger / Dependency Injection
+
+See [dagger.md](docs/dagger.md) and https://dagger.dev/.
+
+## CoreStartable
+
+The starting point for most of SystemUI code is a list of classes that
+implement `CoreStartable` that are started up by SystemUIApplication.
+CoreStartables are like miniature services. They have their `#start` method
+called after being instantiated, and a reference to them is stored inside
+SystemUIApplication. They are in charge of their own behavior beyond this,
+registering and unregistering with the rest of the system as needed.
+
+`CoreStartable` also receives a callback for `#onBootCompleted`
since these objects may be started before the device has finished booting.
-Each SystemUI service is expected to be a major part of system ui and the
-goal is to minimize communication between them. So in general they should be
-relatively silo'd.
+`CoreStartable` is an ideal place to add new features and functionality
+that does not belong directly under the umbrella of an existing feature.
+It is better to define a new `CoreStartable` than to stick unrelated
+initialization code together in catch-all methods.
-## Dependencies
+CoreStartables are tied to application startup via Dagger:
-The first SystemUI service that is started should always be Dependency.
-Dependency provides a static method for getting a hold of dependencies that
-have a lifecycle that spans sysui. Dependency has code for how to create all
-dependencies manually added. SystemUIFactory is also capable of
-adding/replacing these dependencies.
+```kotlin
+class FeatureStartable
+@Inject
+constructor(
+ /* ... */
+) : CoreStartable {
+ override fun start() {
+ // ...
+ }
+}
-Dependencies are lazily initialized, so if a Dependency is never referenced at
-runtime, it will never be created.
+@Module
+abstract class FeatureModule {
+ @Binds
+ @IntoMap
+ @ClassKey(FeatureStartable::class)
+ abstract fun bind(impl: FeatureStartable): CoreStartable
+}
+```
-If an instantiated dependency implements Dumpable it will be included in dumps
-of sysui (and bug reports), allowing it to include current state information.
-This is how \*Controllers dump state to bug reports.
-
-If an instantiated dependency implements ConfigurationChangeReceiver it will
-receive onConfigurationChange callbacks when the configuration changes.
+Including `FeatureModule` in the Dagger graph such as this will ensure that
+`FeatureStartable` gets constructed and that its `#start` method is called.
## IStatusBar
@@ -64,12 +90,6 @@
This is generally used a shortcut to directly trigger CommandQueue rather than
calling StatusManager and waiting for the call to come back to IStatusBar.
-## Default SystemUI services list
-
-### [com.android.systemui.Dependency](/packages/SystemUI/src/com/android/systemui/Dependency.java)
-
-Provides custom dependency injection.
-
### [com.android.systemui.util.NotificationChannels](/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java)
Creates/initializes the channels sysui uses when posting notifications.
@@ -88,11 +108,11 @@
Registers all the callbacks/listeners required to show the Volume dialog when
it should be shown.
-### [com.android.systemui.status.phone.StatusBar](/packages/SystemUI/src/com/android/systemui/status/phone/StatusBar.java)
+### [com.android.systemui.status.phone.CentralSurfaces](/packages/SystemUI/src/com/android/systemui/status/phone/CentralSurfaces.java)
This shows the UI for the status bar and the notification shade it contains.
It also contains a significant amount of other UI that interacts with these
-surfaces (keyguard, AOD, etc.). StatusBar also contains a notification listener
+surfaces (keyguard, AOD, etc.). CentralSurfaces also contains a notification listener
to receive notification callbacks.
### [com.android.systemui.usb.StorageNotification](/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java)
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index f92625b..333aeba 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -16,6 +16,9 @@
},
{
"exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
}
@@ -73,16 +76,16 @@
// Curious where your @Scenario tests will run?
//
// @Ignore: nowhere
- // @Staging or @FlakyTest: in staged-postsubmit, but not postsubmit or
- // presubmit
+ // @FlakyTest: in staged-postsubmit, but not blocking postsubmit or
+ // presubmit
// @Postsubmit: in postsubmit and staged-postsubmit, but not presubmit
// none of the above: in presubmit, postsubmit, and staged-postsubmit
//
- // Therefore, please annotate new tests with @Staging, then with @Postsubmit
- // once they're ready for postsubmit, then with neither once they're ready
- // for presubmit.
+ // Ideally, please annotate new tests with @FlakyTest, then with @Postsubmit
+ // once they're ready for postsubmit as they will immediately block go/android-platinum,
+ // then with neither once they're ready for presubmit.
//
- // If you don't use @Staging or @Postsubmit, your new test will immediately
+ // If you don't use @Postsubmit, your new test will immediately
// block presubmit, which is probably not what you want!
"sysui-platinum-postsubmit": [
{
@@ -99,6 +102,9 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
}
@@ -131,6 +137,9 @@
},
{
"include-filter": "android.platform.test.scenario.sysui"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 9016db7..76139c6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -18,11 +18,17 @@
import android.accessibilityservice.AccessibilityButtonController;
import android.accessibilityservice.AccessibilityService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.view.Display;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
@@ -69,7 +75,7 @@
};
// Update layout.
- private final Handler mHandler = new Handler(getMainLooper());
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Runnable mOnConfigChangedRunnable = new Runnable() {
@Override
public void run() {
@@ -92,11 +98,7 @@
getAccessibilityButtonController().registerAccessibilityButtonCallback(
new AccessibilityButtonController.AccessibilityButtonCallback() {
/**
- * Called when the accessibility button in the system's navigation
- * area is clicked.
- *
- * @param controller the controller used to register for this
- * callback
+ * {@inheritDoc}
*/
@Override
public void onClicked(AccessibilityButtonController controller) {
@@ -107,19 +109,7 @@
}
/**
- * Called when the availability of the accessibility button in the
- * system's
- * navigation area has changed. The accessibility button may become
- * unavailable
- * because the device shopped showing the button, the button was
- * assigned to another
- * service, or for other reasons.
- *
- * @param controller the controller used to register for this
- * callback
- * @param available {@code true} if the accessibility button is
- * available to this
- * service, {@code false} otherwise
+ * {@inheritDoc}
*/
@Override
public void onAvailabilityChanged(AccessibilityButtonController controller,
@@ -141,8 +131,12 @@
protected void onServiceConnected() {
mA11yMenuLayout = new A11yMenuOverlayLayout(this);
- // Temporary measure to force visibility
- mA11yMenuLayout.toggleVisibility();
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mA11yMenuLayout.hideMenu();
+ }}, new IntentFilter(Intent.ACTION_SCREEN_OFF)
+ );
mDisplayManager = getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(mDisplayListener, null);
@@ -184,6 +178,14 @@
}
@Override
+ protected boolean onKeyEvent(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ mA11yMenuLayout.hideMenu();
+ }
+ return false;
+ }
+
+ @Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
if (mA11yMenuLayout.hideMenu()) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 8f70dcc..c729b09 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -301,10 +301,13 @@
interface Callback {
/** Whether we are currently on the keyguard or not. */
- fun isOnKeyguard(): Boolean
+ @JvmDefault fun isOnKeyguard(): Boolean = false
/** Hide the keyguard and animate using [runner]. */
- fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner)
+ @JvmDefault
+ fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) {
+ throw UnsupportedOperationException()
+ }
/* Get the background color of [task]. */
fun getBackgroundColor(task: TaskInfo): Int
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 3d341af..2903288 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -33,9 +33,7 @@
private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
private const val FONT_ITALIC_DEFAULT_VALUE = 0f
-/**
- * Provide interpolation of two fonts by adjusting font variation settings.
- */
+/** Provide interpolation of two fonts by adjusting font variation settings. */
class FontInterpolator {
/**
@@ -61,11 +59,14 @@
var index: Int,
val sortedAxes: MutableList<FontVariationAxis>
) {
- constructor(font: Font, axes: List<FontVariationAxis>) :
- this(font.sourceIdentifier,
- font.ttcIndex,
- axes.toMutableList().apply { sortBy { it.tag } }
- )
+ constructor(
+ font: Font,
+ axes: List<FontVariationAxis>
+ ) : this(
+ font.sourceIdentifier,
+ font.ttcIndex,
+ axes.toMutableList().apply { sortBy { it.tag } }
+ )
fun set(font: Font, axes: List<FontVariationAxis>) {
sourceId = font.sourceIdentifier
@@ -86,9 +87,7 @@
private val tmpInterpKey = InterpKey(null, null, 0f)
private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf())
- /**
- * Linear interpolate the font variation settings.
- */
+ /** Linear interpolate the font variation settings. */
fun lerp(start: Font, end: Font, progress: Float): Font {
if (progress == 0f) {
return start
@@ -115,27 +114,34 @@
// this doesn't take much time since the variation axes is usually up to 5. If we need to
// support more number of axes, we may want to preprocess the font and store the sorted axes
// and also pre-fill the missing axes value with default value from 'fvar' table.
- val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue ->
- when (tag) {
- // TODO: Good to parse 'fvar' table for retrieving default value.
- TAG_WGHT -> adjustWeight(
- MathUtils.lerp(
+ val newAxes =
+ lerp(startAxes, endAxes) { tag, startValue, endValue ->
+ when (tag) {
+ // TODO: Good to parse 'fvar' table for retrieving default value.
+ TAG_WGHT ->
+ adjustWeight(
+ MathUtils.lerp(
startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
- progress))
- TAG_ITAL -> adjustItalic(
- MathUtils.lerp(
+ progress
+ )
+ )
+ TAG_ITAL ->
+ adjustItalic(
+ MathUtils.lerp(
startValue ?: FONT_ITALIC_DEFAULT_VALUE,
endValue ?: FONT_ITALIC_DEFAULT_VALUE,
- progress))
- else -> {
- require(startValue != null && endValue != null) {
- "Unable to interpolate due to unknown default axes value : $tag"
+ progress
+ )
+ )
+ else -> {
+ require(startValue != null && endValue != null) {
+ "Unable to interpolate due to unknown default axes value : $tag"
+ }
+ MathUtils.lerp(startValue, endValue, progress)
}
- MathUtils.lerp(startValue, endValue, progress)
}
}
- }
// Check if we already make font for this axes. This is typically happens if the animation
// happens backward.
@@ -149,9 +155,7 @@
// This is the first time to make the font for the axes. Build and store it to the cache.
// Font.Builder#build won't throw IOException since creating fonts from existing fonts will
// not do any IO work.
- val newFont = Font.Builder(start)
- .setFontVariationSettings(newAxes.toTypedArray())
- .build()
+ val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
interpCache[InterpKey(start, end, progress)] = newFont
verFontCache[VarFontKey(start, newAxes)] = newFont
return newFont
@@ -173,26 +177,28 @@
val tagA = if (i < start.size) start[i].tag else null
val tagB = if (j < end.size) end[j].tag else null
- val comp = when {
- tagA == null -> 1
- tagB == null -> -1
- else -> tagA.compareTo(tagB)
- }
+ val comp =
+ when {
+ tagA == null -> 1
+ tagB == null -> -1
+ else -> tagA.compareTo(tagB)
+ }
- val axis = when {
- comp == 0 -> {
- val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
- FontVariationAxis(tagA, v)
+ val axis =
+ when {
+ comp == 0 -> {
+ val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
+ FontVariationAxis(tagA, v)
+ }
+ comp < 0 -> {
+ val v = filter(tagA!!, start[i++].styleValue, null)
+ FontVariationAxis(tagA, v)
+ }
+ else -> { // comp > 0
+ val v = filter(tagB!!, null, end[j++].styleValue)
+ FontVariationAxis(tagB, v)
+ }
}
- comp < 0 -> {
- val v = filter(tagA!!, start[i++].styleValue, null)
- FontVariationAxis(tagA, v)
- }
- else -> { // comp > 0
- val v = filter(tagB!!, null, end[j++].styleValue)
- FontVariationAxis(tagB, v)
- }
- }
result.add(axis)
}
@@ -202,21 +208,21 @@
// For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache.
private fun adjustWeight(value: Float) =
- coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
+ coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache.
private fun adjustItalic(value: Float) =
- coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
+ coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) =
- (v.coerceIn(min, max) / step).toInt() * step
+ (v.coerceIn(min, max) / step).toInt() * step
companion object {
private val EMPTY_AXES = arrayOf<FontVariationAxis>()
// Returns true if given two font instance can be interpolated.
fun canInterpolate(start: Font, end: Font) =
- start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
+ start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index dfac02d..26aa0e8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -26,6 +26,7 @@
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.InsetDrawable
import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.StateListDrawable
import android.util.Log
import android.view.GhostView
import android.view.View
@@ -35,6 +36,7 @@
import com.android.internal.jank.InteractionJankMonitor
import java.util.LinkedList
import kotlin.math.min
+import kotlin.math.roundToInt
private const val TAG = "GhostedViewLaunchAnimatorController"
@@ -49,7 +51,9 @@
* Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
* whenever possible instead.
*/
-open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
+open class GhostedViewLaunchAnimatorController
+@JvmOverloads
+constructor(
/** The view that will be ghosted and from which the background will be extracted. */
private val ghostedView: View,
@@ -145,7 +149,8 @@
val gradient = findGradientDrawable(drawable) ?: return 0f
// TODO(b/184121838): Support more than symmetric top & bottom radius.
- return gradient.cornerRadii?.get(CORNER_RADIUS_TOP_INDEX) ?: gradient.cornerRadius
+ val radius = gradient.cornerRadii?.get(CORNER_RADIUS_TOP_INDEX) ?: gradient.cornerRadius
+ return radius * ghostedView.scaleX
}
/** Return the current bottom corner radius of the background. */
@@ -154,7 +159,8 @@
val gradient = findGradientDrawable(drawable) ?: return 0f
// TODO(b/184121838): Support more than symmetric top & bottom radius.
- return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
+ val radius = gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
+ return radius * ghostedView.scaleX
}
override fun createAnimatorState(): LaunchAnimator.State {
@@ -173,9 +179,13 @@
ghostedView.getLocationOnScreen(ghostedViewLocation)
val insets = backgroundInsets
state.top = ghostedViewLocation[1] + insets.top
- state.bottom = ghostedViewLocation[1] + ghostedView.height - insets.bottom
+ state.bottom =
+ ghostedViewLocation[1] + (ghostedView.height * ghostedView.scaleY).roundToInt() -
+ insets.bottom
state.left = ghostedViewLocation[0] + insets.left
- state.right = ghostedViewLocation[0] + ghostedView.width - insets.right
+ state.right =
+ ghostedViewLocation[0] + (ghostedView.width * ghostedView.scaleX).roundToInt() -
+ insets.right
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -341,6 +351,10 @@
}
}
+ if (drawable is StateListDrawable) {
+ return findGradientDrawable(drawable.current)
+ }
+
return null
}
}
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 5f1bb83..fdab749 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -36,8 +36,8 @@
* Currently this class can provide text style animation for text weight and text size. For example
* the simple view that draws text with animating text size is like as follows:
*
- * <pre>
- * <code>
+ * <pre> <code>
+ * ```
* class SimpleTextAnimation : View {
* @JvmOverloads constructor(...)
*
@@ -53,83 +53,63 @@
* animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
* }
* }
- * </code>
- * </pre>
+ * ```
+ * </code> </pre>
*/
-class TextAnimator(
- layout: Layout,
- private val invalidateCallback: () -> Unit
-) {
+class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
// Following two members are for mutable for testing purposes.
public var textInterpolator: TextInterpolator = TextInterpolator(layout)
- public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
- duration = DEFAULT_ANIMATION_DURATION
- addUpdateListener {
- textInterpolator.progress = it.animatedValue as Float
- invalidateCallback()
- }
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- textInterpolator.rebase()
+ public var animator: ValueAnimator =
+ ValueAnimator.ofFloat(1f).apply {
+ duration = DEFAULT_ANIMATION_DURATION
+ addUpdateListener {
+ textInterpolator.progress = it.animatedValue as Float
+ invalidateCallback()
}
- override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
- })
- }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ textInterpolator.rebase()
+ }
+ override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
+ }
+ )
+ }
sealed class PositionedGlyph {
- /**
- * Mutable X coordinate of the glyph position relative from drawing offset.
- */
+ /** Mutable X coordinate of the glyph position relative from drawing offset. */
var x: Float = 0f
- /**
- * Mutable Y coordinate of the glyph position relative from the baseline.
- */
+ /** Mutable Y coordinate of the glyph position relative from the baseline. */
var y: Float = 0f
- /**
- * The current line of text being drawn, in a multi-line TextView.
- */
+ /** The current line of text being drawn, in a multi-line TextView. */
var lineNo: Int = 0
- /**
- * Mutable text size of the glyph in pixels.
- */
+ /** Mutable text size of the glyph in pixels. */
var textSize: Float = 0f
- /**
- * Mutable color of the glyph.
- */
+ /** Mutable color of the glyph. */
var color: Int = 0
- /**
- * Immutable character offset in the text that the current font run start.
- */
+ /** Immutable character offset in the text that the current font run start. */
abstract var runStart: Int
protected set
- /**
- * Immutable run length of the font run.
- */
+ /** Immutable run length of the font run. */
abstract var runLength: Int
protected set
- /**
- * Immutable glyph index of the font run.
- */
+ /** Immutable glyph index of the font run. */
abstract var glyphIndex: Int
protected set
- /**
- * Immutable font instance for this font run.
- */
+ /** Immutable font instance for this font run. */
abstract var font: Font
protected set
- /**
- * Immutable glyph ID for this glyph.
- */
+ /** Immutable glyph ID for this glyph. */
abstract var glyphId: Int
protected set
}
@@ -147,20 +127,18 @@
/**
* GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
*
- * This callback is called for each glyphs just before drawing the glyphs. This function will
- * be called with the intrinsic position, size, color, glyph ID and font instance. You can
- * mutate the position, size and color for tweaking animations.
- * Do not keep the reference of passed glyph object. The interpolator reuses that object for
- * avoiding object allocations.
+ * This callback is called for each glyphs just before drawing the glyphs. This function will be
+ * called with the intrinsic position, size, color, glyph ID and font instance. You can mutate
+ * the position, size and color for tweaking animations. Do not keep the reference of passed
+ * glyph object. The interpolator reuses that object for avoiding object allocations.
*
- * Details:
- * The text is drawn with font run units. The font run is a text segment that draws with the
- * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text
- * that current glyph is in. Once the font run is determined, the system will convert characters
- * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and
- * {@code glyphIndex} is the offset of the converted glyph array. Please note that the
- * {@code glyphIndex} is not a character index, because the character will not be converted to
- * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
+ * Details: The text is drawn with font run units. The font run is a text segment that draws
+ * with the same font. The {@code runStart} and {@code runLimit} is a range of the font run in
+ * the text that current glyph is in. Once the font run is determined, the system will convert
+ * characters into glyph IDs. The {@code glyphId} is the glyph identifier in the font and {@code
+ * glyphIndex} is the offset of the converted glyph array. Please note that the {@code
+ * glyphIndex} is not a character index, because the character will not be converted to glyph
+ * one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
* composed from multiple characters.
*
* Here is an example of font runs: "fin. 終わり"
@@ -193,7 +171,9 @@
*/
var glyphFilter: GlyphCallback?
get() = textInterpolator.glyphFilter
- set(value) { textInterpolator.glyphFilter = value }
+ set(value) {
+ textInterpolator.glyphFilter = value
+ }
fun draw(c: Canvas) = textInterpolator.draw(c)
@@ -208,7 +188,7 @@
* @param weight an optional text weight.
* @param textSize an optional font size.
* @param colors an optional colors array that must be the same size as numLines passed to
- * the TextInterpolator
+ * the TextInterpolator
* @param animate an optional boolean indicating true for showing style transition as animation,
* false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -237,10 +217,11 @@
if (weight >= 0) {
// Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
// memory impact, cache the typeface result.
- textInterpolator.targetPaint.typeface = typefaceCache.getOrElse(weight) {
- textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
- textInterpolator.targetPaint.typeface
- }
+ textInterpolator.targetPaint.typeface =
+ typefaceCache.getOrElse(weight) {
+ textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+ textInterpolator.targetPaint.typeface
+ }
}
if (color != null) {
textInterpolator.targetPaint.color = color
@@ -249,22 +230,24 @@
if (animate) {
animator.startDelay = delay
- animator.duration = if (duration == -1L) {
- DEFAULT_ANIMATION_DURATION
- } else {
- duration
- }
+ animator.duration =
+ if (duration == -1L) {
+ DEFAULT_ANIMATION_DURATION
+ } else {
+ duration
+ }
interpolator?.let { animator.interpolator = it }
if (onAnimationEnd != null) {
- val listener = object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- onAnimationEnd.run()
- animator.removeListener(this)
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ onAnimationEnd.run()
+ animator.removeListener(this)
+ }
+ override fun onAnimationCancel(animation: Animator?) {
+ animator.removeListener(this)
+ }
}
- override fun onAnimationCancel(animation: Animator?) {
- animator.removeListener(this)
- }
- }
animator.addListener(listener)
}
animator.start()
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 0448c81..341784e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -26,12 +26,8 @@
import com.android.internal.graphics.ColorUtils
import java.lang.Math.max
-/**
- * Provide text style linear interpolation for plain text.
- */
-class TextInterpolator(
- layout: Layout
-) {
+/** Provide text style linear interpolation for plain text. */
+class TextInterpolator(layout: Layout) {
/**
* Returns base paint used for interpolation.
@@ -64,12 +60,11 @@
var baseFont: Font,
var targetFont: Font
) {
- val length: Int get() = end - start
+ val length: Int
+ get() = end - start
}
- /**
- * A class represents text layout of a single run.
- */
+ /** A class represents text layout of a single run. */
private class Run(
val glyphIds: IntArray,
val baseX: FloatArray, // same length as glyphIds
@@ -79,12 +74,8 @@
val fontRuns: List<FontRun>
)
- /**
- * A class represents text layout of a single line.
- */
- private class Line(
- val runs: List<Run>
- )
+ /** A class represents text layout of a single line. */
+ private class Line(val runs: List<Run>)
private var lines = listOf<Line>()
private val fontInterpolator = FontInterpolator()
@@ -106,8 +97,8 @@
/**
* The layout used for drawing text.
*
- * Only non-styled text is supported. Even if the given layout is created from Spanned, the
- * span information is not used.
+ * Only non-styled text is supported. Even if the given layout is created from Spanned, the span
+ * information is not used.
*
* The paint objects used for interpolation are not changed by this method call.
*
@@ -122,6 +113,9 @@
shapeText(value)
}
+ var shapedText: String = ""
+ private set
+
init {
// shapeText needs to be called after all members are initialized.
shapeText(layout)
@@ -130,8 +124,8 @@
/**
* Recalculate internal text layout for interpolation.
*
- * Whenever the target paint is modified, call this method to recalculate internal
- * text layout used for interpolation.
+ * Whenever the target paint is modified, call this method to recalculate internal text layout
+ * used for interpolation.
*/
fun onTargetPaintModified() {
updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
@@ -140,8 +134,8 @@
/**
* Recalculate internal text layout for interpolation.
*
- * Whenever the base paint is modified, call this method to recalculate internal
- * text layout used for interpolation.
+ * Whenever the base paint is modified, call this method to recalculate internal text layout
+ * used for interpolation.
*/
fun onBasePaintModified() {
updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
@@ -152,11 +146,11 @@
*
* The text interpolator does not calculate all the text position by text shaper due to
* performance reasons. Instead, the text interpolator shape the start and end state and
- * calculate text position of the middle state by linear interpolation. Due to this trick,
- * the text positions of the middle state is likely different from the text shaper result.
- * So, if you want to start animation from the middle state, you will see the glyph jumps due to
- * this trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different
- * from text shape result of weight 550.
+ * calculate text position of the middle state by linear interpolation. Due to this trick, the
+ * text positions of the middle state is likely different from the text shaper result. So, if
+ * you want to start animation from the middle state, you will see the glyph jumps due to this
+ * trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different from
+ * text shape result of weight 550.
*
* After calling this method, do not call onBasePaintModified() since it reshape the text and
* update the base state. As in above notice, the text shaping result at current progress is
@@ -168,8 +162,8 @@
* animate weight from 200 to 400, then if you want to move back to 200 at the half of the
* animation, it will look like
*
- * <pre>
- * <code>
+ * <pre> <code>
+ * ```
* val interp = TextInterpolator(layout)
*
* // Interpolate between weight 200 to 400.
@@ -199,9 +193,8 @@
* // progress is 0.5
* animator.start()
* }
- * </code>
- * </pre>
- *
+ * ```
+ * </code> </pre>
*/
fun rebase() {
if (progress == 0f) {
@@ -263,69 +256,73 @@
}
var maxRunLength = 0
- lines = baseLayout.zip(targetLayout) { baseLine, targetLine ->
- val runs = baseLine.zip(targetLine) { base, target ->
-
- require(base.glyphCount() == target.glyphCount()) {
- "Inconsistent glyph count at line ${lines.size}"
- }
-
- val glyphCount = base.glyphCount()
-
- // Good to recycle the array if the existing array can hold the new layout result.
- val glyphIds = IntArray(glyphCount) {
- base.getGlyphId(it).also { baseGlyphId ->
- require(baseGlyphId == target.getGlyphId(it)) {
- "Inconsistent glyph ID at $it in line ${lines.size}"
+ lines =
+ baseLayout.zip(targetLayout) { baseLine, targetLine ->
+ val runs =
+ baseLine.zip(targetLine) { base, target ->
+ require(base.glyphCount() == target.glyphCount()) {
+ "Inconsistent glyph count at line ${lines.size}"
}
- }
- }
- val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
- val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
- val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
- val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
+ val glyphCount = base.glyphCount()
- // Calculate font runs
- val fontRun = mutableListOf<FontRun>()
- if (glyphCount != 0) {
- var start = 0
- var baseFont = base.getFont(start)
- var targetFont = target.getFont(start)
- require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
- "Cannot interpolate font at $start ($baseFont vs $targetFont)"
- }
-
- for (i in 1 until glyphCount) {
- val nextBaseFont = base.getFont(i)
- val nextTargetFont = target.getFont(i)
-
- if (baseFont !== nextBaseFont) {
- require(targetFont !== nextTargetFont) {
- "Base font has changed at $i but target font has not changed."
+ // Good to recycle the array if the existing array can hold the new layout
+ // result.
+ val glyphIds =
+ IntArray(glyphCount) {
+ base.getGlyphId(it).also { baseGlyphId ->
+ require(baseGlyphId == target.getGlyphId(it)) {
+ "Inconsistent glyph ID at $it in line ${lines.size}"
+ }
+ }
}
- // Font transition point. push run and reset context.
- fontRun.add(FontRun(start, i, baseFont, targetFont))
- maxRunLength = max(maxRunLength, i - start)
- baseFont = nextBaseFont
- targetFont = nextTargetFont
- start = i
+
+ val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
+ val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
+ val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
+ val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
+
+ // Calculate font runs
+ val fontRun = mutableListOf<FontRun>()
+ if (glyphCount != 0) {
+ var start = 0
+ var baseFont = base.getFont(start)
+ var targetFont = target.getFont(start)
require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
"Cannot interpolate font at $start ($baseFont vs $targetFont)"
}
- } else { // baseFont === nextBaseFont
- require(targetFont === nextTargetFont) {
- "Base font has not changed at $i but target font has changed."
+
+ for (i in 1 until glyphCount) {
+ val nextBaseFont = base.getFont(i)
+ val nextTargetFont = target.getFont(i)
+
+ if (baseFont !== nextBaseFont) {
+ require(targetFont !== nextTargetFont) {
+ "Base font has changed at $i but target font is unchanged."
+ }
+ // Font transition point. push run and reset context.
+ fontRun.add(FontRun(start, i, baseFont, targetFont))
+ maxRunLength = max(maxRunLength, i - start)
+ baseFont = nextBaseFont
+ targetFont = nextTargetFont
+ start = i
+ require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
+ "Cannot interpolate font at $start" +
+ " ($baseFont vs $targetFont)"
+ }
+ } else { // baseFont === nextBaseFont
+ require(targetFont === nextTargetFont) {
+ "Base font is unchanged at $i but target font has changed."
+ }
+ }
}
+ fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
+ maxRunLength = max(maxRunLength, glyphCount - start)
}
+ Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
}
- fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
- maxRunLength = max(maxRunLength, glyphCount - start)
- }
- Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
+ Line(runs)
}
- Line(runs)
- }
// Update float array used for drawing.
if (tmpPositionArray.size < maxRunLength * 2) {
@@ -357,9 +354,9 @@
if (glyphFilter == null) {
for (i in run.start until run.end) {
tmpPositionArray[arrayIndex++] =
- MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+ MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
tmpPositionArray[arrayIndex++] =
- MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+ MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
}
c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
return
@@ -388,13 +385,14 @@
tmpPaintForGlyph.color = tmpGlyph.color
c.drawGlyphs(
- line.glyphIds,
- prevStart,
- tmpPositionArray,
- 0,
- i - prevStart,
- font,
- tmpPaintForGlyph)
+ line.glyphIds,
+ prevStart,
+ tmpPositionArray,
+ 0,
+ i - prevStart,
+ font,
+ tmpPaintForGlyph
+ )
prevStart = i
arrayIndex = 0
}
@@ -404,13 +402,14 @@
}
c.drawGlyphs(
- line.glyphIds,
- prevStart,
- tmpPositionArray,
- 0,
- run.end - prevStart,
- font,
- tmpPaintForGlyph)
+ line.glyphIds,
+ prevStart,
+ tmpPositionArray,
+ 0,
+ run.end - prevStart,
+ font,
+ tmpPaintForGlyph
+ )
}
private fun updatePositionsAndFonts(
@@ -418,9 +417,7 @@
updateBase: Boolean
) {
// Update target positions with newly calculated text layout.
- check(layoutResult.size == lines.size) {
- "The new layout result has different line count."
- }
+ check(layoutResult.size == lines.size) { "The new layout result has different line count." }
lines.zip(layoutResult) { line, runs ->
line.runs.zip(runs) { lineRun, newGlyphs ->
@@ -436,7 +433,7 @@
}
require(newFont === newGlyphs.getFont(i)) {
"The new layout has different font run." +
- " $newFont vs ${newGlyphs.getFont(i)} at $i"
+ " $newFont vs ${newGlyphs.getFont(i)} at $i"
}
}
@@ -444,7 +441,7 @@
// check new font can be interpolatable with base font.
require(FontInterpolator.canInterpolate(newFont, run.baseFont)) {
"New font cannot be interpolated with existing font. $newFont," +
- " ${run.baseFont}"
+ " ${run.baseFont}"
}
if (updateBase) {
@@ -480,14 +477,13 @@
}
// Shape the text and stores the result to out argument.
- private fun shapeText(
- layout: Layout,
- paint: TextPaint
- ): List<List<PositionedGlyphs>> {
+ private fun shapeText(layout: Layout, paint: TextPaint): List<List<PositionedGlyphs>> {
+ var text = StringBuilder()
val out = mutableListOf<List<PositionedGlyphs>>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
val lineStart = layout.getLineStart(lineNo)
- var count = layout.getLineEnd(lineNo) - lineStart
+ val lineEnd = layout.getLineEnd(lineNo)
+ var count = lineEnd - lineStart
// Do not render the last character in the line if it's a newline and unprintable
val last = lineStart + count - 1
if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') {
@@ -495,19 +491,28 @@
}
val runs = mutableListOf<PositionedGlyphs>()
- TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
- paint) { _, _, glyphs, _ ->
- runs.add(glyphs)
- }
+ TextShaper.shapeText(
+ layout.text,
+ lineStart,
+ count,
+ layout.textDirectionHeuristic,
+ paint
+ ) { _, _, glyphs, _ -> runs.add(glyphs) }
out.add(runs)
+
+ if (lineNo > 0) {
+ text.append("\n")
+ }
+ text.append(layout.text.substring(lineStart, lineEnd))
}
+ shapedText = text.toString()
return out
}
}
private fun Layout.getDrawOrigin(lineNo: Int) =
- if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
- getLineLeft(lineNo)
- } else {
- getLineRight(lineNo)
- }
+ if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
+ getLineLeft(lineNo)
+ } else {
+ getLineRight(lineNo)
+ }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt
new file mode 100644
index 0000000..1f6e603
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UImportStatement
+
+/**
+ * Detects violations of the Dependency Rule of Clean Architecture.
+ *
+ * The rule states that code in each layer may only depend on code in the same layer or the layer
+ * directly "beneath" that layer in the layer diagram.
+ *
+ * In System UI, we have three layers; from top to bottom, they are: ui, domain, and data. As a
+ * convention, was used packages with those names to place code in the appropriate layer. We also
+ * make an exception and allow for shared models to live under a separate package named "shared" to
+ * avoid code duplication.
+ *
+ * For more information, please see go/sysui-arch.
+ */
+@Suppress("UnstableApiUsage")
+class CleanArchitectureDependencyViolationDetector : Detector(), Detector.UastScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement>> {
+ return listOf(UFile::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ // Check which Clean Architecture layer this file belongs to:
+ matchingLayer(node.packageName)?.let { layer ->
+ // The file matches with a Clean Architecture layer. Let's check all of its
+ // imports.
+ node.imports.forEach { importStatement ->
+ visitImportStatement(context, layer, importStatement)
+ }
+ }
+ }
+ }
+ }
+
+ private fun visitImportStatement(
+ context: JavaContext,
+ layer: Layer,
+ importStatement: UImportStatement,
+ ) {
+ val importText = importStatement.importReference?.asSourceString() ?: return
+ val importedLayer = matchingLayer(importText) ?: return
+
+ // Now check whether the layer of the file may depend on the layer of the import.
+ if (!layer.mayDependOn(importedLayer)) {
+ context.report(
+ issue = ISSUE,
+ scope = importStatement,
+ location = context.getLocation(importStatement),
+ message =
+ "The ${layer.packageNamePart} layer may not depend on" +
+ " the ${importedLayer.packageNamePart} layer.",
+ )
+ }
+ }
+
+ private fun matchingLayer(packageName: String): Layer? {
+ val packageNameParts = packageName.split(".").toSet()
+ return Layer.values()
+ .filter { layer -> packageNameParts.contains(layer.packageNamePart) }
+ .takeIf { it.size == 1 }
+ ?.first()
+ }
+
+ private enum class Layer(
+ val packageNamePart: String,
+ val canDependOn: Set<Layer>,
+ ) {
+ SHARED(
+ packageNamePart = "shared",
+ canDependOn = emptySet(), // The shared layer may not depend on any other layer.
+ ),
+ DATA(
+ packageNamePart = "data",
+ canDependOn = setOf(SHARED),
+ ),
+ DOMAIN(
+ packageNamePart = "domain",
+ canDependOn = setOf(SHARED, DATA),
+ ),
+ UI(
+ packageNamePart = "ui",
+ canDependOn = setOf(DOMAIN, SHARED),
+ ),
+ ;
+
+ fun mayDependOn(otherLayer: Layer): Boolean {
+ return this == otherLayer || canDependOn.contains(otherLayer)
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ val ISSUE =
+ Issue.create(
+ id = "CleanArchitectureDependencyViolation",
+ briefDescription = "Violation of the Clean Architecture Dependency Rule.",
+ explanation =
+ """
+ Following the \"Dependency Rule\" from Clean Architecture, every layer of code \
+ can only depend code in its own layer or code in the layer directly \
+ \"beneath\" it. Therefore, the UI layer can only depend on the" Domain layer \
+ and the Domain layer can only depend on the Data layer. We" do make an \
+ exception to allow shared models to exist and be shared across layers by \
+ placing them under shared/model, which should be done with care. For more \
+ information about Clean Architecture in System UI, please see go/sysui-arch. \
+ NOTE: if your code is not using Clean Architecture, please feel free to ignore \
+ this warning.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ CleanArchitectureDependencyViolationDetector::class.java,
+ Scope.JAVA_FILE_SCOPE,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 3f334c1c..254a6fb 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -27,9 +27,11 @@
class SystemUIIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
- get() = listOf(
+ get() =
+ listOf(
BindServiceOnMainThreadDetector.ISSUE,
BroadcastSentViaContextDetector.ISSUE,
+ CleanArchitectureDependencyViolationDetector.ISSUE,
SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY,
NonInjectedMainThreadDetector.ISSUE,
@@ -37,7 +39,7 @@
SoftwareBitmapDetector.ISSUE,
NonInjectedServiceDetector.ISSUE,
StaticSettingsProviderDetector.ISSUE
- )
+ )
override val api: Int
get() = CURRENT_API
@@ -45,9 +47,9 @@
get() = 8
override val vendor: Vendor =
- Vendor(
- vendorName = "Android",
- feedbackUrl = "http://b/issues/new?component=78010",
- contact = "jernej@google.com"
- )
+ Vendor(
+ vendorName = "Android",
+ feedbackUrl = "http://b/issues/new?component=78010",
+ contact = "jernej@google.com"
+ )
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
new file mode 100644
index 0000000..a4b59fd
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -0,0 +1,296 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Ignore
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+@Ignore("b/254533331")
+class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector {
+ return CleanArchitectureDependencyViolationDetector()
+ }
+
+ override fun getIssues(): List<Issue> {
+ return listOf(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ }
+
+ @Test
+ fun `No violations`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .run()
+ .expectWarningCount(0)
+ }
+
+ @Test
+ fun `Violation - domain depends on ui`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ TestFiles.kotlin(
+ """
+ package test.domain.interactor
+
+ import test.ui.viewmodel.ViewModel
+
+ class BadClass(
+ private val viewModel: ViewModel,
+ )
+ """.trimIndent()
+ )
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expect(
+ expectedText =
+ """
+ src/test/domain/interactor/BadClass.kt:3: Warning: The domain layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
+ import test.ui.viewmodel.ViewModel
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """,
+ )
+ }
+
+ @Test
+ fun `Violation - ui depends on data`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ TestFiles.kotlin(
+ """
+ package test.ui.viewmodel
+
+ import test.data.repository.Repository
+
+ class BadClass(
+ private val repository: Repository,
+ )
+ """.trimIndent()
+ )
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expect(
+ expectedText =
+ """
+ src/test/ui/viewmodel/BadClass.kt:3: Warning: The ui layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
+ import test.data.repository.Repository
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """,
+ )
+ }
+
+ @Test
+ fun `Violation - shared depends on all other layers`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ TestFiles.kotlin(
+ """
+ package test.shared.model
+
+ import test.data.repository.Repository
+ import test.domain.interactor.Interactor
+ import test.ui.viewmodel.ViewModel
+
+ class BadClass(
+ private val repository: Repository,
+ private val interactor: Interactor,
+ private val viewmodel: ViewModel,
+ )
+ """.trimIndent()
+ )
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(3)
+ .expect(
+ expectedText =
+ """
+ src/test/shared/model/BadClass.kt:3: Warning: The shared layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
+ import test.data.repository.Repository
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/shared/model/BadClass.kt:4: Warning: The shared layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
+ import test.domain.interactor.Interactor
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/shared/model/BadClass.kt:5: Warning: The shared layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
+ import test.ui.viewmodel.ViewModel
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 3 warnings
+ """,
+ )
+ }
+
+ @Test
+ fun `Violation - data depends on domain`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ TestFiles.kotlin(
+ """
+ package test.data.repository
+
+ import test.domain.interactor.Interactor
+
+ class BadClass(
+ private val interactor: Interactor,
+ )
+ """.trimIndent()
+ )
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expect(
+ expectedText =
+ """
+ src/test/data/repository/BadClass.kt:3: Warning: The data layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
+ import test.domain.interactor.Interactor
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """,
+ )
+ }
+
+ companion object {
+ private val MODEL_FILE =
+ TestFiles.kotlin(
+ """
+ package test.shared.model
+
+ import test.some.other.thing.SomeOtherThing
+
+ data class Model(
+ private val name: String,
+ )
+ """.trimIndent()
+ )
+ private val REPOSITORY_FILE =
+ TestFiles.kotlin(
+ """
+ package test.data.repository
+
+ import test.shared.model.Model
+ import test.some.other.thing.SomeOtherThing
+
+ class Repository {
+ private val models = listOf(
+ Model("one"),
+ Model("two"),
+ Model("three"),
+ )
+
+ fun getModels(): List<Model> {
+ return models
+ }
+ }
+ """.trimIndent()
+ )
+ private val INTERACTOR_FILE =
+ TestFiles.kotlin(
+ """
+ package test.domain.interactor
+
+ import test.data.repository.Repository
+ import test.shared.model.Model
+
+ class Interactor(
+ private val repository: Repository,
+ ) {
+ fun getModels(): List<Model> {
+ return repository.getModels()
+ }
+ }
+ """.trimIndent()
+ )
+ private val VIEW_MODEL_FILE =
+ TestFiles.kotlin(
+ """
+ package test.ui.viewmodel
+
+ import test.domain.interactor.Interactor
+ import test.some.other.thing.SomeOtherThing
+
+ class ViewModel(
+ private val interactor: Interactor,
+ ) {
+ fun getNames(): List<String> {
+ return interactor.getModels().map { model -> model.name }
+ }
+ }
+ """.trimIndent()
+ )
+ private val NON_CLEAN_ARCHITECTURE_FILE =
+ TestFiles.kotlin(
+ """
+ package test.some.other.thing
+
+ import test.data.repository.Repository
+ import test.domain.interactor.Interactor
+ import test.ui.viewmodel.ViewModel
+
+ class SomeOtherThing {
+ init {
+ val viewModel = ViewModel(
+ interactor = Interactor(
+ repository = Repository(),
+ ),
+ )
+ }
+ }
+ """.trimIndent()
+ )
+ private val LEGITIMATE_FILES =
+ arrayOf(
+ MODEL_FILE,
+ REPOSITORY_FILE,
+ INTERACTOR_FILE,
+ VIEW_MODEL_FILE,
+ NON_CLEAN_ARCHITECTURE_FILE,
+ )
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 7f1c78f..e4e9c46 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -178,6 +178,9 @@
/** Flag denoting whether the customizable clocks feature is enabled. */
const val FLAG_NAME_CUSTOM_CLOCKS_ENABLED = "is_custom_clocks_feature_enabled"
+ /** Flag denoting whether the Wallpaper preview should use the full screen UI. */
+ const val FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW = "wallpaper_fullscreen_preview"
+
object Columns {
/** String. Unique ID for the flag. */
const val NAME = "name"
diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md
index 8917013..9b4c21e 100644
--- a/packages/SystemUI/docs/dagger.md
+++ b/packages/SystemUI/docs/dagger.md
@@ -8,105 +8,110 @@
- [User's guide](https://google.github.io/dagger/users-guide)
-TODO: Add some links.
-
## State of the world
-Dagger 2 has been turned on for SystemUI and a early first pass has been taken
-for converting everything in [Dependency.java](packages/systemui/src/com/android/systemui/Dependency.java)
-to use Dagger. Since a lot of SystemUI depends on Dependency, stubs have been added to Dependency
-to proxy any gets through to the instances provided by dagger, this will allow migration of SystemUI
-through a number of CLs.
+Dagger 2 has been turned on for SystemUI and much of
+[Dependency.java](../src/com/android/systemui/Dependency.java)
+has been converted to use Dagger. Since a lot of SystemUI depends on Dependency,
+stubs have been added to Dependency to proxy any gets through to the instances
+provided by dagger, this will allow migration of SystemUI through a number of CLs.
### How it works in SystemUI
+There are three high level "scopes" of concern in SystemUI. They all represent
+singleton scopes, but serve different purposes.
+
+* `@Singleton` - Instances that are shared everywhere. There isn't a lot of
+ code in this scope. Things like the main thread, and Android Framework
+ provided instances mostly.
+* `@WMShell` - WindowManager related code in the SystemUI process. We don't
+ want this code relying on the rest of SystemUI, and we don't want the rest
+ of SystemUI peeking into its internals, so it runs in its own Subcomponent.
+* `@SysUISingleton` - Most of what would be considered "SystemUI". Most feature
+ work by SystemUI developers goes into this scope. Useful interfaces from
+ WindowManager are made available inside this Subcomponent.
+
+The root dagger graph is created by an instance of `SystemUIInitializer`.
+See [README.md](../README.md) for more details.
For the classes that we're using in Dependency and are switching to dagger, the
equivalent dagger version is using `@Singleton` and therefore only has one instance.
To have the single instance span all of SystemUI and be easily accessible for
other components, there is a single root `@Component` that exists that generates
-these. The component lives in [SystemUIFactory](packages/systemui/src/com/android/systemui/SystemUIFactory.java)
-and is called `SystemUIRootComponent`.
-
-```java
-
-@Singleton
-@Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
- ContextHolder.class})
-public interface SystemUIRootComponent {
- @Singleton
- Dependency.DependencyInjector createDependency();
-}
-```
-
-The root component is composed of root modules, which in turn provide the global singleton
-dependencies across all of SystemUI.
-
-- `SystemUIFactory` `@Provides` dependencies that need to be overridden by SystemUI
-variants (like other form factors e.g. Car).
-
-- `DependencyBinder` creates the mapping from interfaces to implementation classes.
-
-- `DependencyProvider` provides or binds any remaining depedencies required.
-
-### Adding injection to a new SystemUI object
-
-SystemUI object are made injectable by adding an entry in `SystemUIBinder`. SystemUIApplication uses
-information in that file to locate and construct an instance of the requested SystemUI class.
+these. The component lives in
+[ReferenceGlobalRootComponent.java](../src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java).
### Adding a new injectable object
-First tag the constructor with `@Inject`. Also tag it with `@Singleton` if only one
-instance should be created.
+First annotate the constructor with `@Inject`. Also annotate it with
+`@SysUISingleton` if only one instance should be created.
-```java
-@Singleton
-public class SomethingController {
- @Inject
- public SomethingController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
- // context and mainHandler will be automatically populated.
- }
+```kotlin
+@SysUISingleton
+class FeatureStartable
+@Inject
+constructor(
+/* ... */
+) {
+ // ...
}
```
-If you have an interface class and an implementation class, dagger needs to know
-how to map it. The simplest way to do this is to add an `@Provides` method to
-DependencyProvider. The type of the return value tells dagger which dependency it's providing.
+If you have an interface class and an implementation class, Dagger needs to
+know how to map it. The simplest way to do this is to add an `@Binds` method
+in a module. The type of the return value tells dagger which dependency it's
+providing:
-```java
-public class DependencyProvider {
- //...
- @Singleton
- @Provides
- public SomethingController provideSomethingController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
- return new SomethingControllerImpl(context, mainHandler);
- }
+```kotlin
+@Module
+abstract class FeatureModule {
+ @Binds
+ abstract fun bindsFeature(impl: FeatureImpl): Feature
}
```
-If you need to access this from Dependency#get, then add an adapter to Dependency
-that maps to the instance provided by Dagger. The changes should be similar
-to the following diff.
+If you have a class that you want to make injectable that has can not
+be easily constructed by Dagger, write a `@Provides` method for it:
-```java
-public class Dependency {
- //...
- @Inject Lazy<SomethingController> mSomethingController;
- //...
- public void start() {
- //...
- mProviders.put(SomethingController.class, mSomethingController::get);
- }
+```kotlin
+@Module
+abstract class FeatureModule {
+ @Module
+ companion object {
+ @Provides
+ fun providesFeature(ctx: Context): Feature {
+ return FeatureImpl.constructFromContext(ctx)
+ }
+ }
}
```
+### Module Organization
+
+Please define your modules on _at least_ per-package level. If the scope of a
+package grows to encompass a great number of features, create per-feature
+modules.
+
+**Do not create catch-all modules.** Those quickly grow unwieldy and
+unmaintainable. Any that exist today should be refactored into obsolescence.
+
+You can then include your module in one of three places:
+
+1) Within another module that depends on it. Ideally, this creates a clean
+ dependency graph between features and utilities.
+2) For features that should exist in all versions of SystemUI (AOSP and
+ any variants), include the module in
+ [SystemUIModule.java](../src/com/android/systemui/dagger/SystemUIModule.java).
+3) For features that should exist only in AOSP, include the module in
+ [ReferenceSystemUIModule.java](../src/com/android/systemui/dagger/ReferenceSystemUIModule.java).
+ Similarly, if you are working on a custom version of SystemUI and have code
+ specific to your version, include it in a module specific to your version.
+
### Using injection with Fragments
Fragments are created as part of the FragmentManager, so they need to be
setup so the manager knows how to create them. To do that, add a method
to com.android.systemui.fragments.FragmentService$FragmentCreator that
-returns your fragment class. Thats all thats required, once the method
+returns your fragment class. That is all that is required, once the method
exists, FragmentService will automatically pick it up and use injection
whenever your fragment needs to be created.
@@ -123,48 +128,11 @@
FragmentHostManager.get(view).create(NavigationBarFragment.class);
```
-### Using injection with Views
-
-DO NOT ADD NEW VIEW INJECTION. VIEW INJECTION IS BEING ACTIVELY DEPRECATED.
-
-Needing to inject objects into your View's constructor generally implies you
-are doing more work in your presentation layer than is advisable.
-Instead, create an injected controller for you view, inject into the
-controller, and then attach the view to the controller after inflation.
-
-View injection generally causes headaches while testing, as inflating a view
-(which may in turn inflate other views) implicitly causes a Dagger graph to
-be stood up, which may or may not contain the appropriately
-faked/mocked/stubbed objects. It is a hard to control process.
-
## Updating Dagger2
We depend on the Dagger source found in external/dagger2. We should automatically pick up on updates
when that repository is updated.
-
-*Deprecated:*
-
-Binaries can be downloaded from https://repo1.maven.org/maven2/com/google/dagger/ and then loaded
-into
-[/prebuilts/tools/common/m2/repository/com/google/dagger/](http://cs/android/prebuilts/tools/common/m2/repository/com/google/dagger/)
-
-The following commands should work, substituting in the version that you are looking for:
-
-````
-cd prebuilts/tools/common/m2/repository/com/google/dagger/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-spi/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.28.1/
-````
-
-Then update `prebuilts/tools/common/m2/Android.bp` to point at your new jars.
## TODO List
- - Eliminate usages of Dependency#get
- - Add links in above TODO
+ - Eliminate usages of Dependency#get: http://b/hotlists/3940788
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 2148cb0..3d3ccf4 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -1,8 +1,5 @@
+packages/SystemUI
--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
-packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
-packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
@@ -322,8 +319,8 @@
-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/gesture/GenericGestureDetector.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.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
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index dee0f5c..314c736 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -80,6 +80,7 @@
internal class HueVibrantSecondary() : Hue {
val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12),
Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12))
+
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
@@ -88,6 +89,7 @@
internal class HueVibrantTertiary() : Hue {
val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25),
Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25))
+
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
@@ -96,6 +98,7 @@
internal class HueExpressiveSecondary() : Hue {
val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20),
Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45))
+
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
@@ -104,6 +107,7 @@
internal class HueExpressiveTertiary() : Hue {
val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45),
Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120))
+
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
@@ -148,11 +152,11 @@
}
internal class CoreSpec(
- val a1: TonalSpec,
- val a2: TonalSpec,
- val a3: TonalSpec,
- val n1: TonalSpec,
- val n2: TonalSpec
+ val a1: TonalSpec,
+ val a2: TonalSpec,
+ val a3: TonalSpec,
+ val n1: TonalSpec,
+ val n2: TonalSpec
)
enum class Style(internal val coreSpec: CoreSpec) {
@@ -214,51 +218,86 @@
)),
}
+class TonalPalette {
+ val shadeKeys = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
+ val allShades: List<Int>
+ val allShadesMapped: Map<Int, Int>
+ val baseColor: Int
+
+ internal constructor(spec: TonalSpec, seedColor: Int) {
+ val seedCam = Cam.fromInt(seedColor)
+ allShades = spec.shades(seedCam)
+ allShadesMapped = shadeKeys.zip(allShades).toMap()
+
+ val h = spec.hue.get(seedCam).toFloat()
+ val c = spec.chroma.get(seedCam).toFloat()
+ baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor))
+ }
+
+ val s10: Int get() = this.allShades[0]
+ val s50: Int get() = this.allShades[1]
+ val s100: Int get() = this.allShades[2]
+ val s200: Int get() = this.allShades[3]
+ val s300: Int get() = this.allShades[4]
+ val s400: Int get() = this.allShades[5]
+ val s500: Int get() = this.allShades[6]
+ val s600: Int get() = this.allShades[7]
+ val s700: Int get() = this.allShades[8]
+ val s800: Int get() = this.allShades[9]
+ val s900: Int get() = this.allShades[10]
+ val s1000: Int get() = this.allShades[11]
+}
+
class ColorScheme(
- @ColorInt val seed: Int,
- val darkTheme: Boolean,
- val style: Style = Style.TONAL_SPOT
+ @ColorInt val seed: Int,
+ val darkTheme: Boolean,
+ val style: Style = Style.TONAL_SPOT
) {
- val accent1: List<Int>
- val accent2: List<Int>
- val accent3: List<Int>
- val neutral1: List<Int>
- val neutral2: List<Int>
+ val accent1: TonalPalette
+ val accent2: TonalPalette
+ val accent3: TonalPalette
+ val neutral1: TonalPalette
+ val neutral2: TonalPalette
constructor(@ColorInt seed: Int, darkTheme: Boolean) :
this(seed, darkTheme, Style.TONAL_SPOT)
@JvmOverloads
constructor(
- wallpaperColors: WallpaperColors,
- darkTheme: Boolean,
- style: Style = Style.TONAL_SPOT
+ wallpaperColors: WallpaperColors,
+ darkTheme: Boolean,
+ style: Style = Style.TONAL_SPOT
) :
this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
+ val allHues: List<TonalPalette>
+ get() {
+ return listOf(accent1, accent2, accent3, neutral1, neutral2)
+ }
+
val allAccentColors: List<Int>
get() {
val allColors = mutableListOf<Int>()
- allColors.addAll(accent1)
- allColors.addAll(accent2)
- allColors.addAll(accent3)
+ allColors.addAll(accent1.allShades)
+ allColors.addAll(accent2.allShades)
+ allColors.addAll(accent3.allShades)
return allColors
}
val allNeutralColors: List<Int>
get() {
val allColors = mutableListOf<Int>()
- allColors.addAll(neutral1)
- allColors.addAll(neutral2)
+ allColors.addAll(neutral1.allShades)
+ allColors.addAll(neutral2.allShades)
return allColors
}
val backgroundColor
- get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1[8] else neutral1[0], 0xFF)
+ get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1.s700 else neutral1.s10, 0xFF)
val accentColor
- get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF)
+ get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1.s100 else accent1.s500, 0xFF)
init {
val proposedSeedCam = Cam.fromInt(seed)
@@ -269,24 +308,26 @@
} else {
seed
}
- val camSeed = Cam.fromInt(seedArgb)
- accent1 = style.coreSpec.a1.shades(camSeed)
- accent2 = style.coreSpec.a2.shades(camSeed)
- accent3 = style.coreSpec.a3.shades(camSeed)
- neutral1 = style.coreSpec.n1.shades(camSeed)
- neutral2 = style.coreSpec.n2.shades(camSeed)
+
+ accent1 = TonalPalette(style.coreSpec.a1, seedArgb)
+ accent2 = TonalPalette(style.coreSpec.a2, seedArgb)
+ accent3 = TonalPalette(style.coreSpec.a3, seedArgb)
+ neutral1 = TonalPalette(style.coreSpec.n1, seedArgb)
+ neutral2 = TonalPalette(style.coreSpec.n2, seedArgb)
}
+ val shadeCount get() = this.accent1.allShades.size
+
override fun toString(): String {
return "ColorScheme {\n" +
" seed color: ${stringForColor(seed)}\n" +
" style: $style\n" +
" palettes: \n" +
- " ${humanReadable("PRIMARY", accent1)}\n" +
- " ${humanReadable("SECONDARY", accent2)}\n" +
- " ${humanReadable("TERTIARY", accent3)}\n" +
- " ${humanReadable("NEUTRAL", neutral1)}\n" +
- " ${humanReadable("NEUTRAL VARIANT", neutral2)}\n" +
+ " ${humanReadable("PRIMARY", accent1.allShades)}\n" +
+ " ${humanReadable("SECONDARY", accent2.allShades)}\n" +
+ " ${humanReadable("TERTIARY", accent3.allShades)}\n" +
+ " ${humanReadable("NEUTRAL", neutral1.allShades)}\n" +
+ " ${humanReadable("NEUTRAL VARIANT", neutral2.allShades)}\n" +
"}"
}
@@ -385,7 +426,8 @@
val existingSeedNearby = seeds.find {
val hueA = intToCam[int]!!.hue
val hueB = intToCam[it]!!.hue
- hueDiff(hueA, hueB) < i } != null
+ hueDiff(hueA, hueB) < i
+ } != null
if (existingSeedNearby) {
continue
}
@@ -460,9 +502,9 @@
}
private fun huePopulations(
- camByColor: Map<Int, Cam>,
- populationByColor: Map<Int, Double>,
- filter: Boolean = true
+ camByColor: Map<Int, Cam>,
+ populationByColor: Map<Int, Double>,
+ filter: Boolean = true
): List<Double> {
val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
index d3fabac..faf1b78 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
@@ -21,6 +21,7 @@
import android.net.Uri
import android.os.Handler
import android.os.Looper
+import android.os.Trace
import android.provider.Settings
/**
@@ -51,14 +52,21 @@
}
}
+ private fun clearCache() {
+ Trace.beginSection("LogcatEchoTrackerDebug#clearCache")
+ cachedBufferLevels.clear()
+ Trace.endSection()
+ }
+
private fun attach(mainLooper: Looper) {
+ Trace.beginSection("LogcatEchoTrackerDebug#attach")
contentResolver.registerContentObserver(
Settings.Global.getUriFor(BUFFER_PATH),
true,
object : ContentObserver(Handler(mainLooper)) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
- cachedBufferLevels.clear()
+ clearCache()
}
}
)
@@ -69,10 +77,11 @@
object : ContentObserver(Handler(mainLooper)) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
- cachedTagLevels.clear()
+ clearCache()
}
}
)
+ Trace.endSection()
}
/** Whether [bufferName] should echo messages of [level] or higher to logcat. */
@@ -97,9 +106,12 @@
private fun readSetting(path: String): LogLevel {
return try {
+ Trace.beginSection("LogcatEchoTrackerDebug#readSetting")
parseProp(Settings.Global.getString(contentResolver, path))
} catch (_: Settings.SettingNotFoundException) {
DEFAULT_LEVEL
+ } finally {
+ Trace.endSection()
}
}
diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml
new file mode 100644
index 0000000..ef1406c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_camera.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M7,42Q5.8,42 4.9,41.1Q4,40.2 4,39V13.35Q4,12.15 4.9,11.25Q5.8,10.35 7,10.35H14.35L18,6H30L33.65,10.35H41Q42.2,10.35 43.1,11.25Q44,12.15 44,13.35V39Q44,40.2 43.1,41.1Q42.2,42 41,42ZM7,39H41Q41,39 41,39Q41,39 41,39V13.35Q41,13.35 41,13.35Q41,13.35 41,13.35H7Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM7,39Q7,39 7,39Q7,39 7,39V13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM24,34.7Q27.5,34.7 30,32.225Q32.5,29.75 32.5,26.2Q32.5,22.7 30,20.2Q27.5,17.7 24,17.7Q20.45,17.7 17.975,20.2Q15.5,22.7 15.5,26.2Q15.5,29.75 17.975,32.225Q20.45,34.7 24,34.7ZM24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
similarity index 92%
copy from packages/SystemUI/res/drawable/ic_note_task_button.xml
copy to packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
index bb5e224..ee8d4883 100644
--- a/packages/SystemUI/res/drawable/ic_note_task_button.xml
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
@@ -19,10 +19,13 @@
android:viewportHeight="24"
android:viewportWidth="24">
<path
- android:fillColor="#636C6F"
+ android:fillAlpha="1"
+ android:fillColor="@android:color/white"
+ android:fillType="nonZero"
android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
<path
- android:fillColor="#636C6F"
- android:fillType="evenOdd"
+ android:fillAlpha="1"
+ android:fillColor="@android:color/white"
+ android:fillType="nonZero"
android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
similarity index 95%
rename from packages/SystemUI/res/drawable/ic_note_task_button.xml
rename to packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
index bb5e224..7590182 100644
--- a/packages/SystemUI/res/drawable/ic_note_task_button.xml
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
@@ -19,10 +19,13 @@
android:viewportHeight="24"
android:viewportWidth="24">
<path
+ android:fillAlpha="1"
android:fillColor="#636C6F"
+ android:fillType="nonZero"
android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
<path
+ android:fillAlpha="1"
android:fillColor="#636C6F"
- android:fillType="evenOdd"
+ android:fillType="nonZero"
android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_videocam.xml b/packages/SystemUI/res/drawable/ic_videocam.xml
new file mode 100644
index 0000000..de2bc7b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_videocam.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H33Q34.2,8 35.1,8.9Q36,9.8 36,11V21.75L44,13.75V34.25L36,26.25V37Q36,38.2 35.1,39.1Q34.2,40 33,40ZM7,37H33Q33,37 33,37Q33,37 33,37V11Q33,11 33,11Q33,11 33,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
index 18fcebb..87b5a4c 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
@@ -16,53 +16,13 @@
* limitations under the License.
*/
-->
-<selector
+<shape
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-
- <item android:state_selected="true">
- <layer-list>
- <item
- android:left="3dp"
- android:top="3dp"
- android:right="3dp"
- android:bottom="3dp">
- <shape android:shape="oval">
- <solid android:color="?androidprv:attr/colorSurface"/>
- <size
- android:width="@dimen/keyguard_affordance_width"
- android:height="@dimen/keyguard_affordance_height"/>
- </shape>
- </item>
-
- <item>
- <shape android:shape="oval">
- <stroke
- android:color="@color/control_primary_text"
- android:width="2dp"/>
- <size
- android:width="@dimen/keyguard_affordance_width"
- android:height="@dimen/keyguard_affordance_height"/>
- </shape>
- </item>
- </layer-list>
- </item>
-
- <item>
- <layer-list>
- <item
- android:left="3dp"
- android:top="3dp"
- android:right="3dp"
- android:bottom="3dp">
- <shape android:shape="oval">
- <solid android:color="?androidprv:attr/colorSurface"/>
- <size
- android:width="@dimen/keyguard_affordance_width"
- android:height="@dimen/keyguard_affordance_height"/>
- </shape>
- </item>
- </layer-list>
- </item>
-
-</selector>
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <size
+ android:width="@dimen/keyguard_affordance_fixed_width"
+ android:height="@dimen/keyguard_affordance_fixed_height"/>
+ <corners android:radius="@dimen/keyguard_affordance_fixed_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml
new file mode 100644
index 0000000..acd2462
--- /dev/null
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 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_selected="true">
+ <shape android:shape="oval">
+ <stroke
+ android:color="@color/control_primary_text"
+ android:width="2dp"/>
+ <size
+ android:width="@dimen/keyguard_affordance_fixed_width"
+ android:height="@dimen/keyguard_affordance_fixed_height"/>
+ </shape>
+ </item>
+</selector>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 6120863..3f95515 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -67,6 +67,7 @@
android:scaleType="center"
android:tint="?android:attr/textColorPrimary"
android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
android:visibility="gone" />
@@ -79,6 +80,7 @@
android:scaleType="center"
android:tint="?android:attr/textColorPrimary"
android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 21d12c2..4483db8 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -27,6 +27,14 @@
android:layout_height="wrap_content"
/>
+ <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+ android:id="@+id/icon_glow_ripple"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <!-- Add a bottom margin to avoid the glow of the icon ripple from being cropped by screen
+ bounds while animating with the icon -->
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
android:background="@drawable/media_ttt_chip_background_receiver"
@@ -34,6 +42,7 @@
android:layout_height="@dimen/media_ttt_icon_size_receiver"
android:layout_gravity="center|bottom"
android:alpha="0.0"
+ android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index bbb8df1c..db94c92 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -26,6 +26,17 @@
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/unlock_prompt_footer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:drawablePadding="8dp"
+ android:visibility="gone"
+ android:textAppearance="?android:attr/textAppearanceButton"
+ android:text="@string/unlock_to_see_notif_text"/>
<com.android.systemui.statusbar.notification.row.FooterViewButton
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/manage_text"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index f4434e8..ea3c012 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -36,4 +36,15 @@
<integer name="qs_security_footer_maxLines">1</integer>
<bool name="config_use_large_screen_shade_header">true</bool>
+
+ <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
+ string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
+ separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
+ default is displayed by System UI as long as the user hasn't made a different choice for that
+ slot. If the user did make a choice, even if the choice is the "None" option, the default is
+ ignored. -->
+ <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+ <item>bottom_start:home</item>
+ <item>bottom_end:create_note</item>
+ </string-array>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 9bc0dde..db7fb48 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -30,10 +30,6 @@
<!-- Margin on the left side of the carrier text on Keyguard -->
<dimen name="keyguard_carrier_text_margin">24dp</dimen>
- <!-- The width/height of the phone/camera/unlock icon on keyguard. -->
- <dimen name="keyguard_affordance_height">80dp</dimen>
- <dimen name="keyguard_affordance_width">120dp</dimen>
-
<!-- Screen pinning request width -->
<dimen name="screen_pinning_request_width">400dp</dimen>
<!-- Screen pinning request bottom button circle widths -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fc67015..f122805 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -762,12 +762,10 @@
<dimen name="go_to_full_shade_appearing_translation">200dp</dimen>
<!-- The width/height of the keyguard bottom area icon view on keyguard. -->
- <dimen name="keyguard_affordance_height">48dp</dimen>
- <dimen name="keyguard_affordance_width">48dp</dimen>
-
<dimen name="keyguard_affordance_fixed_height">48dp</dimen>
<dimen name="keyguard_affordance_fixed_width">48dp</dimen>
<dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
+
<!-- Amount the button should shake when it's not long-pressed for long enough. -->
<dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
@@ -1089,6 +1087,7 @@
(112 - 40) / 2 = 36dp -->
<dimen name="media_ttt_generic_icon_padding">36dp</dimen>
<dimen name="media_ttt_receiver_vert_translation">40dp</dimen>
+ <dimen name="media_ttt_receiver_icon_bottom_margin">10dp</dimen>
<!-- Window magnification -->
<dimen name="magnification_border_drag_size">35dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 337b209..3d3bb6e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2899,6 +2899,12 @@
<!-- Subtitle for the notification sent when a stylus battery is low. [CHAR LIMIT=none]-->
<string name="stylus_battery_low_subtitle">Connect your stylus to a charger</string>
+ <!-- Title for notification of low stylus battery. [CHAR_LIMIT=NONE] -->
+ <string name="stylus_battery_low">Stylus battery low</string>
+
+ <!-- Label for a lock screen shortcut to start the camera in video mode. [CHAR_LIMIT=16] -->
+ <string name="video_camera">Video camera</string>
+
<!-- Switch to work profile dialer app for placing a call dialog. -->
<!-- Text for Switch to work profile dialog's Title. Switch to work profile dialog guide users to make call from work
profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 8a0fca0..28e786b 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -91,6 +91,9 @@
static_libs: [
"SystemUI-flag-types",
],
+ optimize: {
+ proguard_flags_files: ["proguard_flags.flags"],
+ },
java_version: "1.8",
min_sdk_version: "current",
}
diff --git a/packages/SystemUI/shared/proguard_flags.flags b/packages/SystemUI/shared/proguard_flags.flags
new file mode 100644
index 0000000..08859cd
--- /dev/null
+++ b/packages/SystemUI/shared/proguard_flags.flags
@@ -0,0 +1 @@
+-keep class * implements com.android.systemui.flags.ParcelableFlag
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index fa484c7..a71fb56 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -37,7 +37,7 @@
/**
* Sent when overview is to be shown.
*/
- void onOverviewShown(boolean triggeredFromAltTab, boolean forward) = 7;
+ void onOverviewShown(boolean triggeredFromAltTab) = 7;
/**
* Sent when overview is to be hidden.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 1c532fe..b8bddd1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -22,6 +22,7 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.view.MotionEvent;
+import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.shared.recents.model.Task;
@@ -87,12 +88,6 @@
void notifyPrioritizedRotation(int rotation) = 25;
/**
- * Handle the provided image as if it was a screenshot.
- */
- void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen,
- in Insets visibleInsets, in Task.TaskKey task) = 28;
-
- /**
* Notifies to expand notification panel.
*/
void expandNotificationPanel() = 29;
@@ -125,5 +120,10 @@
*/
void toggleNotificationPanel() = 50;
- // Next id = 51
+ /**
+ * Handle the screenshot request.
+ */
+ void takeScreenshot(in ScreenshotRequest request) = 51;
+
+ // Next id = 52
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 08567a7..6d9c5df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -199,7 +199,7 @@
}
mSecureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
+ Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
false, /* notifyForDescendants */
mDoubleLineClockObserver,
UserHandle.USER_ALL
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
new file mode 100644
index 0000000..249b3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+/** Logging helper for trust repository. */
+@SysUISingleton
+class TrustRepositoryLogger
+@Inject
+constructor(
+ @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer,
+) {
+ fun onTrustChanged(
+ enabled: Boolean,
+ newlyUnlocked: Boolean,
+ userId: Int,
+ flags: Int,
+ trustGrantedMessages: List<String>?
+ ) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = enabled
+ bool2 = newlyUnlocked
+ int1 = userId
+ int2 = flags
+ str1 = trustGrantedMessages?.joinToString()
+ },
+ {
+ "onTrustChanged enabled: $bool1, newlyUnlocked: $bool2, " +
+ "userId: $int1, flags: $int2, grantMessages: $str1"
+ }
+ )
+ }
+
+ fun trustListenerRegistered() {
+ logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#registerTrustListener")
+ }
+
+ fun trustListenerUnregistered() {
+ logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#unregisterTrustListener")
+ }
+
+ fun trustModelEmitted(value: TrustModel) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = value.userId
+ bool1 = value.isTrusted
+ },
+ { "trustModel emitted: userId: $int1 isTrusted: $bool1" }
+ )
+ }
+
+ fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isCurrentUserTrusted },
+ { "isCurrentUserTrusted emitted: $bool1" }
+ )
+ }
+
+ companion object {
+ const val TAG = "TrustRepositoryLog"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
index fbb909f..2c97d62 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
@@ -27,6 +27,7 @@
import android.util.Log;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -67,8 +68,8 @@
}
@Inject
- public AccessibilityButtonModeObserver(Context context) {
- super(context, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
+ public AccessibilityButtonModeObserver(Context context, UserTracker userTracker) {
+ super(context, userTracker, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
index b32ebcc..53a21b3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
@@ -23,6 +23,7 @@
import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
import javax.inject.Inject;
@@ -48,8 +49,8 @@
}
@Inject
- public AccessibilityButtonTargetsObserver(Context context) {
- super(context, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+ public AccessibilityButtonTargetsObserver(Context context, UserTracker userTracker) {
+ super(context, userTracker, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
index e4e0da6..326773f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
@@ -27,6 +27,7 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
import java.util.List;
@@ -44,6 +45,7 @@
public abstract class SecureSettingsContentObserver<T> {
private final ContentResolver mContentResolver;
+ private final UserTracker mUserTracker;
@VisibleForTesting
final ContentObserver mContentObserver;
@@ -52,9 +54,11 @@
@VisibleForTesting
final List<T> mListeners = new ArrayList<>();
- protected SecureSettingsContentObserver(Context context, String secureSettingsKey) {
+ protected SecureSettingsContentObserver(Context context, UserTracker userTracker,
+ String secureSettingsKey) {
mKey = secureSettingsKey;
mContentResolver = context.getContentResolver();
+ mUserTracker = userTracker;
mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
@@ -103,7 +107,7 @@
* See {@link Settings.Secure}.
*/
public final String getSettingsValue() {
- return Settings.Secure.getStringForUser(mContentResolver, mKey, UserHandle.USER_CURRENT);
+ return Settings.Secure.getStringForUser(mContentResolver, mKey, mUserTracker.getUserId());
}
private void updateValueChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 50449b0..c5d4bbe 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -35,14 +35,12 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
@@ -52,6 +50,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -179,6 +178,7 @@
private final SystemActionsBroadcastReceiver mReceiver;
private final Context mContext;
+ private final UserTracker mUserTracker;
private final Optional<Recents> mRecentsOptional;
private Locale mLocale;
private final AccessibilityManager mA11yManager;
@@ -190,11 +190,13 @@
@Inject
public SystemActions(Context context,
+ UserTracker userTracker,
NotificationShadeWindowController notificationShadeController,
ShadeController shadeController,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Optional<Recents> recentsOptional) {
mContext = context;
+ mUserTracker = userTracker;
mShadeController = shadeController;
mRecentsOptional = recentsOptional;
mReceiver = new SystemActionsBroadcastReceiver();
@@ -343,6 +345,7 @@
/**
* Register a system action.
+ *
* @param actionId the action ID to register.
*/
public void register(int actionId) {
@@ -440,6 +443,7 @@
/**
* Unregister a system action.
+ *
* @param actionId the action ID to unregister.
*/
public void unregister(int actionId) {
@@ -475,7 +479,8 @@
}
private void handleNotifications() {
- mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::animateExpandNotificationsPanel);
+ mCentralSurfacesOptionalLazy.get().ifPresent(
+ CentralSurfaces::animateExpandNotificationsPanel);
}
private void handleQuickSettings() {
@@ -507,7 +512,7 @@
private void handleTakeScreenshot() {
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ screenshotHelper.takeScreenshot(
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
@@ -525,7 +530,7 @@
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
}
private void handleAccessibilityShortcut() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 9537ce0..348c2ee 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -41,6 +41,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
@@ -74,15 +75,18 @@
private final Handler mHandler;
private final WindowMagnifierCallback mWindowMagnifierCallback;
private final SysUiState mSysUiState;
+ private final SecureSettings mSecureSettings;
ControllerSupplier(Context context, Handler handler,
WindowMagnifierCallback windowMagnifierCallback,
- DisplayManager displayManager, SysUiState sysUiState) {
+ DisplayManager displayManager, SysUiState sysUiState,
+ SecureSettings secureSettings) {
super(displayManager);
mContext = context;
mHandler = handler;
mWindowMagnifierCallback = windowMagnifierCallback;
mSysUiState = sysUiState;
+ mSecureSettings = secureSettings;
}
@Override
@@ -99,7 +103,8 @@
new SurfaceControl.Transaction(),
mWindowMagnifierCallback,
mSysUiState,
- WindowManagerGlobal::getWindowSession);
+ WindowManagerGlobal::getWindowSession,
+ mSecureSettings);
}
}
@@ -109,7 +114,8 @@
@Inject
public WindowMagnification(Context context, @Main Handler mainHandler,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
- SysUiState sysUiState, OverviewProxyService overviewProxyService) {
+ SysUiState sysUiState, OverviewProxyService overviewProxyService,
+ SecureSettings secureSettings) {
mContext = context;
mHandler = mainHandler;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
@@ -118,7 +124,8 @@
mSysUiState = sysUiState;
mOverviewProxyService = overviewProxyService;
mMagnificationControllerSupplier = new ControllerSupplier(context,
- mHandler, this, context.getSystemService(DisplayManager.class), sysUiState);
+ mHandler, this, context.getSystemService(DisplayManager.class), sysUiState,
+ secureSettings);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a3dbaad..74f5f13 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -76,6 +76,7 @@
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.text.NumberFormat;
@@ -230,7 +231,8 @@
SurfaceControl.Transaction transaction,
@NonNull WindowMagnifierCallback callback,
SysUiState sysUiState,
- @NonNull Supplier<IWindowSession> globalWindowSessionSupplier) {
+ @NonNull Supplier<IWindowSession> globalWindowSessionSupplier,
+ SecureSettings secureSettings) {
mContext = context;
mHandler = handler;
mAnimationController = animationController;
@@ -249,7 +251,7 @@
mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
mResources = mContext.getResources();
- mScale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ mScale = secureSettings.getFloatForUser(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
mResources.getInteger(R.integer.magnification_default_scale),
UserHandle.USER_CURRENT);
@@ -274,7 +276,7 @@
mWindowMagnificationSettings =
new WindowMagnificationSettings(mContext, mWindowMagnificationSettingsCallback,
- mSfVsyncFrameProvider);
+ mSfVsyncFrameProvider, secureSettings);
// Initialize listeners.
mMirrorViewRunnable = () -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 56602ad..527c124 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -53,6 +53,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
+import com.android.systemui.util.settings.SecureSettings;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -66,6 +67,7 @@
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
+ private final SecureSettings mSecureSettings;
private final Runnable mWindowInsetChangeRunnable;
private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@@ -110,14 +112,15 @@
@VisibleForTesting
WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
- SfVsyncFrameCallbackProvider sfVsyncFrameProvider) {
+ SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings) {
mContext = context;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mWindowManager = mContext.getSystemService(WindowManager.class);
mSfVsyncFrameProvider = sfVsyncFrameProvider;
mCallback = callback;
+ mSecureSettings = secureSettings;
- mAllowDiagonalScrolling = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ mAllowDiagonalScrolling = mSecureSettings.getIntForUser(
Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
UserHandle.USER_CURRENT) == 1;
@@ -133,7 +136,7 @@
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
- Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ mSecureSettings.putFloatForUser(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
UserHandle.USER_CURRENT);
mCallback.onMagnifierScale(scale);
@@ -388,7 +391,7 @@
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_seekbar);
mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
- float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ float scale = mSecureSettings.getFloatForUser(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
UserHandle.USER_CURRENT);
setSeekbarProgress(scale);
@@ -510,11 +513,11 @@
}
private void toggleDiagonalScrolling() {
- boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ boolean enabled = mSecureSettings.getIntForUser(
Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
UserHandle.USER_CURRENT) == 1;
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ mSecureSettings.putIntForUser(
Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 0 : 1,
UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
index de351ec..81d7766 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -43,6 +43,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.List;
@@ -60,6 +61,7 @@
private static final float DEFAULT_POSITION_Y_PERCENT = 0.77f;
private final Context mContext;
+ private final SecureSettings mSecureSettings;
private final AccessibilityFloatingMenuView mMenuView;
private final MigrationTooltipView mMigrationTooltipView;
private final DockTooltipView mDockTooltipView;
@@ -77,7 +79,7 @@
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- mMenuView.setSizeType(getSizeType(mContext));
+ mMenuView.setSizeType(getSizeType());
}
};
@@ -85,8 +87,8 @@
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
- getOpacityValue(mContext));
+ mMenuView.updateOpacityWith(isFadeEffectEnabled(),
+ getOpacityValue());
}
};
@@ -98,16 +100,19 @@
}
};
- public AccessibilityFloatingMenu(Context context) {
+ public AccessibilityFloatingMenu(Context context, SecureSettings secureSettings) {
mContext = context;
+ mSecureSettings = secureSettings;
mMenuView = new AccessibilityFloatingMenuView(context, getPosition(context));
mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
mDockTooltipView = new DockTooltipView(mContext, mMenuView);
}
@VisibleForTesting
- AccessibilityFloatingMenu(Context context, AccessibilityFloatingMenuView menuView) {
+ AccessibilityFloatingMenu(Context context, SecureSettings secureSettings,
+ AccessibilityFloatingMenuView menuView) {
mContext = context;
+ mSecureSettings = secureSettings;
mMenuView = menuView;
mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
mDockTooltipView = new DockTooltipView(mContext, mMenuView);
@@ -130,10 +135,10 @@
mMenuView.show();
mMenuView.onTargetsChanged(targetList);
- mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
- getOpacityValue(mContext));
- mMenuView.setSizeType(getSizeType(mContext));
- mMenuView.setShapeType(getShapeType(mContext));
+ mMenuView.updateOpacityWith(isFadeEffectEnabled(),
+ getOpacityValue());
+ mMenuView.setSizeType(getSizeType());
+ mMenuView.setShapeType(getShapeType());
mMenuView.setOnDragEndListener(this::onDragEnd);
showMigrationTooltipIfNecessary();
@@ -170,17 +175,17 @@
// Migration tooltip was the android S feature. It's just used on the Android version from R
// to S. In addition, it only shows once.
private void showMigrationTooltipIfNecessary() {
- if (isMigrationTooltipPromptEnabled(mContext)) {
+ if (isMigrationTooltipPromptEnabled()) {
mMigrationTooltipView.show();
- Settings.Secure.putInt(mContext.getContentResolver(),
+ mSecureSettings.putInt(
ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, /* disabled */ 0);
}
}
- private static boolean isMigrationTooltipPromptEnabled(Context context) {
- return Settings.Secure.getInt(
- context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
+ private boolean isMigrationTooltipPromptEnabled() {
+ return mSecureSettings.getInt(
+ ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1;
}
@@ -212,57 +217,61 @@
}
}
- private static boolean isFadeEffectEnabled(Context context) {
- return Settings.Secure.getInt(
- context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ private boolean isFadeEffectEnabled() {
+ return mSecureSettings.getInt(
+ ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
DEFAULT_FADE_EFFECT_IS_ENABLED) == /* enabled */ 1;
}
- private static float getOpacityValue(Context context) {
- return Settings.Secure.getFloat(
- context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_OPACITY,
+ private float getOpacityValue() {
+ return mSecureSettings.getFloat(
+ ACCESSIBILITY_FLOATING_MENU_OPACITY,
DEFAULT_OPACITY_VALUE);
}
- private static int getSizeType(Context context) {
- return Settings.Secure.getInt(
- context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
+ private int getSizeType() {
+ return mSecureSettings.getInt(
+ ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
}
- private static int getShapeType(Context context) {
- return Settings.Secure.getInt(
- context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
+ private int getShapeType() {
+ return mSecureSettings.getInt(
+ ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
ShapeType.OVAL);
}
private void registerContentObservers() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
/* notifyForDescendants */ false, mContentObserver,
UserHandle.USER_CURRENT);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ /* notifyForDescendants */ false, mContentObserver,
+ UserHandle.USER_CURRENT);
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
/* notifyForDescendants */ false, mSizeContentObserver,
UserHandle.USER_CURRENT);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
/* notifyForDescendants */ false, mFadeOutContentObserver,
UserHandle.USER_CURRENT);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
/* notifyForDescendants */ false, mFadeOutContentObserver,
UserHandle.USER_CURRENT);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES),
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
/* notifyForDescendants */ false,
mEnabledA11yServicesContentObserver, UserHandle.USER_CURRENT);
}
private void unregisterContentObservers() {
- mContext.getContentResolver().unregisterContentObserver(mContentObserver);
- mContext.getContentResolver().unregisterContentObserver(mSizeContentObserver);
- mContext.getContentResolver().unregisterContentObserver(mFadeOutContentObserver);
- mContext.getContentResolver().unregisterContentObserver(
+ mSecureSettings.unregisterContentObserver(mContentObserver);
+ mSecureSettings.unregisterContentObserver(mSizeContentObserver);
+ mSecureSettings.unregisterContentObserver(mFadeOutContentObserver);
+ mSecureSettings.unregisterContentObserver(
mEnabledA11yServicesContentObserver);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 6d54d38..7fedf16 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -40,6 +40,7 @@
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
@@ -59,6 +60,7 @@
private final DisplayManager mDisplayManager;
private final AccessibilityManager mAccessibilityManager;
private final FeatureFlags mFeatureFlags;
+ private final SecureSettings mSecureSettings;
@VisibleForTesting
IAccessibilityFloatingMenu mFloatingMenu;
private int mBtnMode;
@@ -102,7 +104,8 @@
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ SecureSettings secureSettings) {
mContext = context;
mWindowManager = windowManager;
mDisplayManager = displayManager;
@@ -111,6 +114,7 @@
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mFeatureFlags = featureFlags;
+ mSecureSettings = secureSettings;
mIsKeyguardVisible = false;
}
@@ -185,9 +189,9 @@
final Context windowContext = mContext.createWindowContext(defaultDisplay,
TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
- mAccessibilityManager);
+ mAccessibilityManager, mSecureSettings);
} else {
- mFloatingMenu = new AccessibilityFloatingMenu(mContext);
+ mFloatingMenu = new AccessibilityFloatingMenu(mContext, mSecureSettings);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index f79c3d2..aad708a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -49,6 +49,7 @@
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
+import com.android.systemui.util.settings.SecureSettings;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -77,6 +78,7 @@
mA11yServicesStateChangeListener = manager -> onTargetFeaturesChanged();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final OnSettingsContentsChanged mSettingsContentsCallback;
+ private final SecureSettings mSecureSettings;
private Position mPercentagePosition;
@IntDef({
@@ -104,7 +106,7 @@
@Override
public void onChange(boolean selfChange) {
mSettingsContentsCallback.onSizeTypeChanged(
- getMenuSizeTypeFromSettings(mContext));
+ getMenuSizeTypeFromSettings());
}
};
@@ -142,11 +144,12 @@
};
MenuInfoRepository(Context context, AccessibilityManager accessibilityManager,
- OnSettingsContentsChanged settingsContentsChanged) {
+ OnSettingsContentsChanged settingsContentsChanged, SecureSettings secureSettings) {
mContext = context;
mAccessibilityManager = accessibilityManager;
mConfiguration = new Configuration(context.getResources().getConfiguration());
mSettingsContentsCallback = settingsContentsChanged;
+ mSecureSettings = secureSettings;
mPercentagePosition = getStartPosition();
}
@@ -164,7 +167,7 @@
}
void loadMigrationTooltipVisibility(OnInfoReady<Boolean> callback) {
- callback.onReady(Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ callback.onReady(mSecureSettings.getIntForUser(
ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT, UserHandle.USER_CURRENT)
== MigrationPrompt.ENABLED);
@@ -179,7 +182,7 @@
}
void loadMenuSizeType(OnInfoReady<Integer> callback) {
- callback.onReady(getMenuSizeTypeFromSettings(mContext));
+ callback.onReady(getMenuSizeTypeFromSettings());
}
void loadMenuFadeEffectInfo(OnInfoReady<MenuFadeEffectInfo> callback) {
@@ -187,8 +190,8 @@
}
private MenuFadeEffectInfo getMenuFadeEffectInfo() {
- return new MenuFadeEffectInfo(isMenuFadeEffectEnabledFromSettings(mContext),
- getMenuOpacityFromSettings(mContext));
+ return new MenuFadeEffectInfo(isMenuFadeEffectEnabledFromSettings(),
+ getMenuOpacityFromSettings());
}
void updateMoveToTucked(boolean isMoveToTucked) {
@@ -208,7 +211,7 @@
}
void updateMigrationTooltipVisibility(boolean visible) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ mSecureSettings.putIntForUser(
ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
visible ? MigrationPrompt.ENABLED : MigrationPrompt.DISABLED,
UserHandle.USER_CURRENT);
@@ -229,24 +232,25 @@
}
void registerObserversAndCallbacks() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
+ mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
/* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
UserHandle.USER_CURRENT);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
+ mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
/* notifyForDescendants */ false,
- mMenuTargetFeaturesContentObserver, UserHandle.USER_CURRENT);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
+ mMenuTargetFeaturesContentObserver,
+ UserHandle.USER_CURRENT);
+ mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
/* notifyForDescendants */ false, mMenuSizeContentObserver,
UserHandle.USER_CURRENT);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
+ mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
/* notifyForDescendants */ false, mMenuFadeOutContentObserver,
UserHandle.USER_CURRENT);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY),
+ mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY),
/* notifyForDescendants */ false, mMenuFadeOutContentObserver,
UserHandle.USER_CURRENT);
mContext.registerComponentCallbacks(mComponentCallbacks);
@@ -277,19 +281,19 @@
void onReady(T info);
}
- private static int getMenuSizeTypeFromSettings(Context context) {
- return Settings.Secure.getIntForUser(context.getContentResolver(),
+ private int getMenuSizeTypeFromSettings() {
+ return mSecureSettings.getIntForUser(
ACCESSIBILITY_FLOATING_MENU_SIZE, SMALL, UserHandle.USER_CURRENT);
}
- private static boolean isMenuFadeEffectEnabledFromSettings(Context context) {
- return Settings.Secure.getIntForUser(context.getContentResolver(),
+ private boolean isMenuFadeEffectEnabledFromSettings() {
+ return mSecureSettings.getIntForUser(
ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
DEFAULT_FADE_EFFECT_IS_ENABLED, UserHandle.USER_CURRENT) == /* enabled */ 1;
}
- private static float getMenuOpacityFromSettings(Context context) {
- return Settings.Secure.getFloatForUser(context.getContentResolver(),
+ private float getMenuOpacityFromSettings() {
+ return mSecureSettings.getFloatForUser(
ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY_VALUE,
UserHandle.USER_CURRENT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 15a8d09..d0c426d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -58,6 +58,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
+import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -87,6 +88,7 @@
private final AccessibilityManager mAccessibilityManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
+ private final SecureSettings mSecureSettings;
private final DismissAnimationController mDismissAnimationController;
private final MenuViewModel mMenuViewModel;
private final Observer<Boolean> mDockTooltipObserver =
@@ -126,7 +128,7 @@
final Runnable mDismissMenuAction = new Runnable() {
@Override
public void run() {
- Settings.Secure.putStringForUser(getContext().getContentResolver(),
+ mSecureSettings.putStringForUser(
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
UserHandle.USER_CURRENT);
@@ -147,7 +149,8 @@
};
MenuViewLayer(@NonNull Context context, WindowManager windowManager,
- AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
+ AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu,
+ SecureSettings secureSettings) {
super(context);
// Simplifies the translation positioning and animations
@@ -156,8 +159,9 @@
mWindowManager = windowManager;
mAccessibilityManager = accessibilityManager;
mFloatingMenu = floatingMenu;
+ mSecureSettings = secureSettings;
- mMenuViewModel = new MenuViewModel(context, accessibilityManager);
+ mMenuViewModel = new MenuViewModel(context, accessibilityManager, secureSettings);
mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
mMenuAnimationController = mMenuView.getMenuAnimationController();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index c7be907..c52ecc5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -24,6 +24,8 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import com.android.systemui.util.settings.SecureSettings;
+
/**
* Controls the {@link MenuViewLayer} whether to be attached to the window via the interface
* of {@link IAccessibilityFloatingMenu}.
@@ -34,9 +36,10 @@
private boolean mIsShowing;
MenuViewLayerController(Context context, WindowManager windowManager,
- AccessibilityManager accessibilityManager) {
+ AccessibilityManager accessibilityManager, SecureSettings secureSettings) {
mWindowManager = windowManager;
- mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this);
+ mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this,
+ secureSettings);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index eec8467..f924784 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -23,6 +23,7 @@
import androidx.lifecycle.MutableLiveData;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.List;
@@ -42,9 +43,10 @@
private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
private final MenuInfoRepository mInfoRepository;
- MenuViewModel(Context context, AccessibilityManager accessibilityManager) {
+ MenuViewModel(Context context, AccessibilityManager accessibilityManager,
+ SecureSettings secureSettings) {
mInfoRepository = new MenuInfoRepository(context,
- accessibilityManager, /* settingsContentsChanged= */ this);
+ accessibilityManager, /* settingsContentsChanged= */ this, secureSettings);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 57ffdab..86f0d06 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -35,8 +35,10 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
@@ -119,6 +121,8 @@
private final UiController mUiController;
protected final Lazy<SysUiState> mSysUiState;
protected final AssistLogger mAssistLogger;
+ private final UserTracker mUserTracker;
+ private final SecureSettings mSecureSettings;
private final DeviceProvisionedController mDeviceProvisionedController;
private final CommandQueue mCommandQueue;
@@ -135,7 +139,9 @@
Lazy<SysUiState> sysUiState,
DefaultUiController defaultUiController,
AssistLogger assistLogger,
- @Main Handler uiHandler) {
+ @Main Handler uiHandler,
+ UserTracker userTracker,
+ SecureSettings secureSettings) {
mContext = context;
mDeviceProvisionedController = controller;
mCommandQueue = commandQueue;
@@ -143,6 +149,8 @@
mAssistDisclosure = new AssistDisclosure(context, uiHandler);
mPhoneStateMonitor = phoneStateMonitor;
mAssistLogger = assistLogger;
+ mUserTracker = userTracker;
+ mSecureSettings = secureSettings;
registerVoiceInteractionSessionListener();
@@ -273,7 +281,7 @@
CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
false /* force */);
- boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ boolean structureEnabled = mSecureSettings.getIntForUser(
Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
final SearchManager searchManager =
@@ -300,7 +308,7 @@
@Override
public void run() {
mContext.startActivityAsUser(intent, opts.toBundle(),
- new UserHandle(UserHandle.USER_CURRENT));
+ mUserTracker.getUserHandle());
}
});
} catch (ActivityNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 79c09fd..7217f99 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -97,6 +97,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
@@ -166,6 +167,7 @@
@Nullable private final TouchProcessor mTouchProcessor;
@NonNull private final SessionTracker mSessionTracker;
@NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+ @NonNull private final SecureSettings mSecureSettings;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -261,7 +263,7 @@
mLockscreenShadeTransitionController, mConfigurationController,
mKeyguardStateController,
mUnlockedScreenOffAnimationController,
- mUdfpsDisplayMode, requestId, reason, callback,
+ mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
mPrimaryBouncerInteractor, mAlternateBouncerInteractor)));
@@ -834,7 +836,8 @@
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
@NonNull SessionTracker sessionTracker,
- @NonNull AlternateBouncerInteractor alternateBouncerInteractor) {
+ @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
+ @NonNull SecureSettings secureSettings) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -875,6 +878,7 @@
mBiometricExecutor = biometricsExecutor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
+ mSecureSettings = secureSettings;
mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
? singlePointerTouchProcessor : null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 1b6c8c6..b4b3fae 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -61,6 +61,7 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
private const val TAG = "UdfpsControllerOverlay"
@@ -90,6 +91,7 @@
private val keyguardStateController: KeyguardStateController,
private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+ private val secureSettings: SecureSettings,
val requestId: Long,
@ShowReason val requestReason: Int,
private val controllerCallback: IUdfpsOverlayControllerCallback,
@@ -132,7 +134,7 @@
/** A helper if the [requestReason] was due to enrollment. */
val enrollHelper: UdfpsEnrollHelper? =
if (requestReason.isEnrollmentReason() && !shouldRemoveEnrollmentUi()) {
- UdfpsEnrollHelper(context, fingerprintManager, requestReason)
+ UdfpsEnrollHelper(context, fingerprintManager, secureSettings, requestReason)
} else {
null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index d5c763d3..cfa8ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -24,11 +24,12 @@
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
import android.util.TypedValue;
import android.view.accessibility.AccessibilityManager;
+import com.android.systemui.util.settings.SecureSettings;
+
import java.util.ArrayList;
import java.util.List;
@@ -51,8 +52,8 @@
void onLastStepAcquired();
}
- @NonNull private final Context mContext;
@NonNull private final FingerprintManager mFingerprintManager;
+ @NonNull private final SecureSettings mSecureSettings;
// IUdfpsOverlayController reason
private final int mEnrollReason;
private final boolean mAccessibilityEnabled;
@@ -70,10 +71,11 @@
@Nullable Listener mListener;
public UdfpsEnrollHelper(@NonNull Context context,
- @NonNull FingerprintManager fingerprintManager, int reason) {
+ @NonNull FingerprintManager fingerprintManager, SecureSettings secureSettings,
+ int reason) {
- mContext = context;
mFingerprintManager = fingerprintManager;
+ mSecureSettings = secureSettings;
mEnrollReason = reason;
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
@@ -84,8 +86,7 @@
// Number of pixels per mm
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
context.getResources().getDisplayMetrics());
- boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- NEW_COORDS_OVERRIDE, 0,
+ boolean useNewCoords = mSecureSettings.getIntForUser(NEW_COORDS_OVERRIDE, 0,
UserHandle.USER_CURRENT) != 0;
if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
Log.v(TAG, "Using new coordinates");
@@ -210,8 +211,7 @@
float scale = SCALE;
if (Build.IS_ENG || Build.IS_USERDEBUG) {
- scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
- SCALE_OVERRIDE, SCALE,
+ scale = mSecureSettings.getFloatForUser(SCALE_OVERRIDE, SCALE,
UserHandle.USER_CURRENT);
}
final int index = mLocationsEnrolled - mCenterTouchCount;
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index 58d40d3..4227a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -25,13 +25,13 @@
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.RemoteException
-import android.os.UserHandle
import android.util.Log
import android.view.WindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -55,6 +55,7 @@
private val cameraIntents: CameraIntentsWrapper,
private val contentResolver: ContentResolver,
@Main private val uiExecutor: Executor,
+ private val userTracker: UserTracker
) {
/**
* Whether the camera application can be launched for the camera launch gesture.
@@ -111,7 +112,7 @@
Intent.FLAG_ACTIVITY_NEW_TASK,
null,
activityOptions.toBundle(),
- UserHandle.CURRENT.identifier,
+ userTracker.userId,
)
} catch (e: RemoteException) {
Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index 867faf9..cc43e7e 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -20,15 +20,13 @@
import android.content.Intent
import android.provider.MediaStore
import android.text.TextUtils
-
import com.android.systemui.R
class CameraIntents {
companion object {
- val DEFAULT_SECURE_CAMERA_INTENT_ACTION =
- MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
- val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
- MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ val DEFAULT_SECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
+ val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ private val VIDEO_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_VIDEO_CAMERA
const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
@JvmStatic
@@ -44,18 +42,14 @@
@JvmStatic
fun getInsecureCameraIntent(context: Context): Intent {
val intent = Intent(DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
- getOverrideCameraPackage(context)?.let {
- intent.setPackage(it)
- }
+ getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
return intent
}
@JvmStatic
fun getSecureCameraIntent(context: Context): Intent {
val intent = Intent(DEFAULT_SECURE_CAMERA_INTENT_ACTION)
- getOverrideCameraPackage(context)?.let {
- intent.setPackage(it)
- }
+ getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
return intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
}
@@ -68,5 +62,11 @@
fun isInsecureCameraIntent(intent: Intent?): Boolean {
return intent?.getAction()?.equals(DEFAULT_INSECURE_CAMERA_INTENT_ACTION) ?: false
}
+
+ /** Returns an [Intent] that can be used to start the camera in video mode. */
+ @JvmStatic
+ fun getVideoCameraIntent(): Intent {
+ return Intent(VIDEO_CAMERA_INTENT_ACTION)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
index cf02f8f..a434617 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -21,7 +21,9 @@
import javax.inject.Inject
/** Injectable wrapper around [CameraIntents]. */
-class CameraIntentsWrapper @Inject constructor(
+class CameraIntentsWrapper
+@Inject
+constructor(
private val context: Context,
) {
@@ -40,4 +42,9 @@
fun getInsecureCameraIntent(): Intent {
return CameraIntents.getInsecureCameraIntent(context)
}
+
+ /** Returns an [Intent] that can be used to start the camera in video mode. */
+ fun getVideoCameraIntent(): Intent {
+ return CameraIntents.getVideoCameraIntent()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 1454210..fb0c0a6 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -20,7 +20,7 @@
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.os.SystemProperties
-import android.util.DisplayMetrics
+import android.view.Surface
import android.view.View
import android.view.WindowManager
import com.android.internal.annotations.VisibleForTesting
@@ -36,7 +36,6 @@
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.leak.RotationUtils
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import javax.inject.Inject
@@ -172,30 +171,28 @@
}
private fun layoutRipple() {
- val displayMetrics = DisplayMetrics()
- context.display.getRealMetrics(displayMetrics)
- val width = displayMetrics.widthPixels
- val height = displayMetrics.heightPixels
+ val bounds = windowManager.currentWindowMetrics.bounds
+ val width = bounds.width()
+ val height = bounds.height()
val maxDiameter = Integer.max(width, height) * 2f
rippleView.setMaxSize(maxDiameter, maxDiameter)
- when (RotationUtils.getExactRotation(context)) {
- RotationUtils.ROTATION_LANDSCAPE -> {
+ when (context.display.rotation) {
+ Surface.ROTATION_0 -> {
+ rippleView.setCenter(
+ width * normalizedPortPosX, height * normalizedPortPosY)
+ }
+ Surface.ROTATION_90 -> {
rippleView.setCenter(
width * normalizedPortPosY, height * (1 - normalizedPortPosX))
}
- RotationUtils.ROTATION_UPSIDE_DOWN -> {
+ Surface.ROTATION_180 -> {
rippleView.setCenter(
width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
}
- RotationUtils.ROTATION_SEASCAPE -> {
+ Surface.ROTATION_270 -> {
rippleView.setCenter(
width * (1 - normalizedPortPosY), height * normalizedPortPosX)
}
- else -> {
- // ROTATION_NONE
- rippleView.setCenter(
- width * normalizedPortPosX, height * normalizedPortPosY)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index 2a3d67f..c331164 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -17,12 +17,12 @@
package com.android.systemui.doze;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.UserHandle;
import android.util.Log;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.settings.UserTracker;
import java.io.PrintWriter;
@@ -40,14 +40,17 @@
private final AmbientDisplayConfiguration mConfig;
private DozeMachine mMachine;
private final DockManager mDockManager;
+ private final UserTracker mUserTracker;
private final DockEventListener mDockEventListener;
private int mDockState = DockManager.STATE_NONE;
@Inject
- DozeDockHandler(AmbientDisplayConfiguration config, DockManager dockManager) {
+ DozeDockHandler(AmbientDisplayConfiguration config, DockManager dockManager,
+ UserTracker userTracker) {
mConfig = config;
mDockManager = dockManager;
+ mUserTracker = userTracker;
mDockEventListener = new DockEventListener();
}
@@ -100,7 +103,7 @@
nextState = State.DOZE_AOD_DOCKED;
break;
case DockManager.STATE_NONE:
- nextState = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) ? State.DOZE_AOD
+ nextState = mConfig.alwaysOnEnabled(mUserTracker.getUserId()) ? State.DOZE_AOD
: State.DOZE;
break;
case DockManager.STATE_DOCKED_HIDE:
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 96c35d4..fc3263f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -23,7 +23,6 @@
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Trace;
-import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
@@ -33,6 +32,7 @@
import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -149,6 +149,7 @@
private final DozeHost mDozeHost;
private final DockManager mDockManager;
private final Part[] mParts;
+ private final UserTracker mUserTracker;
private final ArrayList<State> mQueuedRequests = new ArrayList<>();
private State mState = State.UNINITIALIZED;
@@ -161,7 +162,7 @@
AmbientDisplayConfiguration ambientDisplayConfig,
WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
DozeLog dozeLog, DockManager dockManager,
- DozeHost dozeHost, Part[] parts) {
+ DozeHost dozeHost, Part[] parts, UserTracker userTracker) {
mDozeService = service;
mAmbientDisplayConfig = ambientDisplayConfig;
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -170,6 +171,7 @@
mDockManager = dockManager;
mDozeHost = dozeHost;
mParts = parts;
+ mUserTracker = userTracker;
for (Part part : parts) {
part.setDozeMachine(this);
}
@@ -429,7 +431,7 @@
nextState = State.FINISH;
} else if (mDockManager.isDocked()) {
nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
- } else if (mAmbientDisplayConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+ } else if (mAmbientDisplayConfig.alwaysOnEnabled(mUserTracker.getUserId())) {
nextState = State.DOZE_AOD;
} else {
nextState = State.DOZE;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 937884c..4cade77 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -43,6 +43,7 @@
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.settings.SystemSettings;
import java.io.PrintWriter;
import java.util.Objects;
@@ -78,6 +79,7 @@
private final DozeParameters mDozeParameters;
private final DevicePostureController mDevicePostureController;
private final DozeLog mDozeLog;
+ private final SystemSettings mSystemSettings;
private final int[] mSensorToBrightness;
private final int[] mSensorToScrimOpacity;
private final int mScreenBrightnessDim;
@@ -110,7 +112,8 @@
WakefulnessLifecycle wakefulnessLifecycle,
DozeParameters dozeParameters,
DevicePostureController devicePostureController,
- DozeLog dozeLog) {
+ DozeLog dozeLog,
+ SystemSettings systemSettings) {
mContext = context;
mDozeService = service;
mSensorManager = sensorManager;
@@ -122,6 +125,7 @@
mDozeHost = host;
mHandler = handler;
mDozeLog = dozeLog;
+ mSystemSettings = systemSettings;
mScreenBrightnessMinimumDimAmountFloat = context.getResources().getFloat(
R.dimen.config_screenBrightnessMinimumDimAmountFloat);
@@ -257,7 +261,7 @@
}
//TODO: brightnessfloat change usages to float.
private int clampToUserSetting(int brightness) {
- int userSetting = Settings.System.getIntForUser(mContext.getContentResolver(),
+ int userSetting = mSystemSettings.getIntForUser(
Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE,
UserHandle.USER_CURRENT);
return Math.min(brightness, userSetting);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 72c7cf5..0d3503e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -255,7 +255,7 @@
new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
mConfig.wakeScreenGestureAvailable()
- && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT),
+ && mConfig.alwaysOnEnabled(mUserTracker.getUserId()),
DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
false /* reports touch coordinates */,
false /* touchscreen */
@@ -296,7 +296,7 @@
private boolean udfpsLongPressConfigured() {
return mUdfpsEnrolled
- && (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) || mScreenOffUdfpsEnabled);
+ && (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) || mScreenOffUdfpsEnabled);
}
private boolean quickPickUpConfigured() {
@@ -697,13 +697,13 @@
}
protected boolean enabledBySetting() {
- if (!mConfig.enabled(UserHandle.USER_CURRENT)) {
+ if (!mConfig.enabled(mUserTracker.getUserId())) {
return false;
} else if (TextUtils.isEmpty(mSetting)) {
return true;
}
return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0,
- UserHandle.USER_CURRENT) != 0;
+ mUserTracker.getUserId()) != 0;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
index e6d9865..de0bdd3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
@@ -20,10 +20,10 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.PowerManager;
-import android.os.UserHandle;
import android.text.TextUtils;
import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import java.io.PrintWriter;
@@ -57,6 +57,7 @@
private final AmbientDisplayConfiguration mConfig;
private final DozeLog mDozeLog;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+ private final UserTracker mUserTracker;
private boolean mIsCarModeEnabled = false;
@@ -65,11 +66,13 @@
DozeHost dozeHost,
AmbientDisplayConfiguration config,
DozeLog dozeLog,
- Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
+ Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+ UserTracker userTracker) {
mDozeHost = dozeHost;
mConfig = config;
mDozeLog = dozeLog;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+ mUserTracker = userTracker;
}
@Override
@@ -148,7 +151,7 @@
private void handleCarModeExited() {
mDozeLog.traceCarModeEnded();
- mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
+ mMachine.requestState(mConfig.alwaysOnEnabled(mUserTracker.getUserId())
? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
}
@@ -166,7 +169,7 @@
if (mDozeHost.isPowerSaveActive()) {
nextState = DozeMachine.State.DOZE;
} else if (mMachine.getState() == DozeMachine.State.DOZE
- && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+ && mConfig.alwaysOnEnabled(mUserTracker.getUserId())) {
nextState = DozeMachine.State.DOZE_AOD;
}
@@ -181,7 +184,7 @@
// handles suppression changes, while DozeMachine#transitionPolicy handles gating
// transitions to DOZE_AOD
final DozeMachine.State nextState;
- if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) && !suppressed) {
+ if (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) && !suppressed) {
nextState = DozeMachine.State.DOZE_AOD;
} else {
nextState = DozeMachine.State.DOZE;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index fdc115b..27641fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -234,6 +234,13 @@
});
}
+ @Override
+ public void onEndDream() {
+ mExecutor.execute(() -> {
+ resetCurrentDreamOverlayLocked();
+ });
+ }
+
private Lifecycle.State getCurrentStateLocked() {
return mLifecycleRegistry.getCurrentState();
}
@@ -317,6 +324,7 @@
mDreamOverlayContainerViewController = null;
mDreamOverlayTouchMonitor = null;
+ mWindow = null;
mStarted = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index f1bb156..90c440c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -25,7 +25,6 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.PluralsMessageFormatter;
@@ -37,6 +36,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -72,6 +72,7 @@
mDreamOverlayNotificationCountProvider;
private final ZenModeController mZenModeController;
private final DreamOverlayStateController mDreamOverlayStateController;
+ private final UserTracker mUserTracker;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
private final Executor mMainExecutor;
@@ -154,7 +155,8 @@
ZenModeController zenModeController,
StatusBarWindowStateController statusBarWindowStateController,
DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
- DreamOverlayStateController dreamOverlayStateController) {
+ DreamOverlayStateController dreamOverlayStateController,
+ UserTracker userTracker) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
@@ -169,6 +171,7 @@
mStatusBarItemsProvider = statusBarItemsProvider;
mZenModeController = zenModeController;
mDreamOverlayStateController = dreamOverlayStateController;
+ mUserTracker = userTracker;
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -259,7 +262,7 @@
private void updateAlarmStatusIcon() {
final AlarmManager.AlarmClockInfo alarm =
- mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+ mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f1e45cb..7718329 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -64,6 +64,10 @@
// TODO(b/259130119): Tracking Bug
val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true)
+ // TODO(b/265804648): Tracking Bug
+ @JvmField
+ val DISABLE_FSI = unreleasedFlag(265804648, "disable_fsi")
+
// TODO(b/254512538): Tracking Bug
val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true)
@@ -104,7 +108,8 @@
// TODO(b/263414400): Tracking Bug
@JvmField
- val NOTIFICATION_ANIMATE_BIG_PICTURE = unreleasedFlag(120, "notification_animate_big_picture")
+ val NOTIFICATION_ANIMATE_BIG_PICTURE =
+ unreleasedFlag(120, "notification_animate_big_picture", teamfood = true)
// 200 - keyguard/lockscreen
// ** Flag retired **
@@ -212,6 +217,11 @@
val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS =
unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false)
+ // TODO(b/242908637): Tracking Bug
+ @JvmField
+ val WALLPAPER_FULLSCREEN_PREVIEW =
+ unreleasedFlag(227, "wallpaper_fullscreen_preview", teamfood = true)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -225,7 +235,8 @@
// TODO(b/258517050): Clean up after the feature is launched.
@JvmField
- val SMARTSPACE_DATE_WEATHER_DECOUPLED = unreleasedFlag(403, "smartspace_date_weather_decoupled")
+ val SMARTSPACE_DATE_WEATHER_DECOUPLED =
+ sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = false)
// 500 - quick settings
@@ -339,6 +350,10 @@
// TODO(b/263512203): Tracking Bug
val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true)
+ // TODO(b/265813373): Tracking Bug
+ val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE =
+ unreleasedFlag(912, "media_ttt_dismiss_gesture", teamfood = true)
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -352,7 +367,7 @@
@Keep
@JvmField
val WM_ENABLE_SHELL_TRANSITIONS =
- sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = false)
+ sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = true)
// TODO(b/254513207): Tracking Bug
@Keep
@@ -454,6 +469,16 @@
val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM =
unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
+ // TODO(b/238475428): Tracking Bug
+ @JvmField
+ val WM_SHADE_ALLOW_BACK_GESTURE =
+ unreleasedFlag(1207, "persist.wm.debug.shade_allow_back_gesture", teamfood = false)
+
+ // TODO(b/238475428): Tracking Bug
+ @JvmField
+ val WM_SHADE_ANIMATE_BACK_GESTURE =
+ unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = true)
+
// 1300 - screenshots
// TODO(b/254513155): Tracking Bug
@JvmField
@@ -533,6 +558,13 @@
val OUTPUT_SWITCHER_SHOW_API_ENABLED =
unreleasedFlag(2503, "output_switcher_show_api_enabled", teamfood = true)
+ // 2700 - unfold transitions
+ // TODO(b/265764985): Tracking Bug
+ @Keep
+ @JvmField
+ val ENABLE_DARK_VIGNETTE_WHEN_FOLDING =
+ unreleasedFlag(2700, "enable_dark_vignette_when_folding")
+
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index dccd94a..949bcfb 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -21,7 +21,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
@@ -902,7 +901,7 @@
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
}
}
}
@@ -960,8 +959,7 @@
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
+ mScreenshotHelper.takeScreenshot(SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 4f1a2b3..ad7973e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -24,7 +24,6 @@
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.hardware.input.InputManager;
@@ -53,6 +52,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -108,6 +108,7 @@
protected volatile Context mContext;
private final Provider<LocalBluetoothManager> mBluetoothManagerProvider;
+ private final SecureSettings mSecureSettings;
private boolean mEnabled;
private String mKeyboardName;
@@ -125,9 +126,11 @@
private int mState;
@Inject
- public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider) {
+ public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider,
+ SecureSettings secureSettings) {
mContext = context;
this.mBluetoothManagerProvider = bluetoothManagerProvider;
+ mSecureSettings = secureSettings;
}
@Override
@@ -298,9 +301,8 @@
}
private boolean isUserSetupComplete() {
- ContentResolver resolver = mContext.getContentResolver();
- return Secure.getIntForUser(
- resolver, Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+ return mSecureSettings.getIntForUser(
+ Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
}
private CachedBluetoothDevice getPairedKeyboard() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 18854e5..0dbc930 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -148,12 +148,12 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
-import dagger.Lazy;
-
/**
* Mediates requests related to the keyguard. This includes queries about the
* state of the keyguard, power management events that effect whether the keyguard
@@ -589,12 +589,6 @@
@Override
public void onDeviceProvisioned() {
sendUserPresentBroadcast();
- synchronized (KeyguardViewMediator.this) {
- // If system user is provisioned, we might want to lock now to avoid showing launcher
- if (mustNotUnlockCurrentUser()) {
- doKeyguardLocked(null);
- }
- }
}
@Override
@@ -1265,11 +1259,6 @@
mPM.userActivity(SystemClock.uptimeMillis(), false);
}
- boolean mustNotUnlockCurrentUser() {
- return UserManager.isSplitSystemUser()
- && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM;
- }
-
private void setupLocked() {
mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
mShowKeyguardWakeLock.setReferenceCounted(false);
@@ -1342,7 +1331,7 @@
mHideAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.lock_screen_behind_enter);
- mWorkLockController = new WorkLockActivityController(mContext);
+ mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
}
@Override
@@ -1947,31 +1936,28 @@
}
}
- // In split system user mode, we never unlock system user.
- if (!mustNotUnlockCurrentUser()
- || !mUpdateMonitor.isDeviceProvisioned()) {
+ // if the setup wizard hasn't run yet, don't show
+ final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
+ final boolean absent = SubscriptionManager.isValidSubscriptionId(
+ mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_ABSENT));
+ final boolean disabled = SubscriptionManager.isValidSubscriptionId(
+ mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PERM_DISABLED));
+ final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
+ || ((absent || disabled) && requireSim);
- // if the setup wizard hasn't run yet, don't show
- final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
- final boolean absent = SubscriptionManager.isValidSubscriptionId(
- mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_ABSENT));
- final boolean disabled = SubscriptionManager.isValidSubscriptionId(
- mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PERM_DISABLED));
- final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
- || ((absent || disabled) && requireSim);
-
- if (!lockedOrMissing && shouldWaitForProvisioning()) {
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
- + " and the sim is not locked or missing");
- return;
+ if (!lockedOrMissing && shouldWaitForProvisioning()) {
+ if (DEBUG) {
+ Log.d(TAG, "doKeyguard: not showing because device isn't provisioned and the sim is"
+ + " not locked or missing");
}
+ return;
+ }
- boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
- if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
- && !lockedOrMissing && !forceShow) {
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
- return;
- }
+ boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
+ if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
+ && !lockedOrMissing && !forceShow) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+ return;
}
if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
@@ -2289,6 +2275,10 @@
}
if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) {
handleKeyguardDone();
+ } else if (mSurfaceBehindRemoteAnimationRunning) {
+ // We're already running the keyguard exit animation, likely due to an in-progress swipe
+ // to unlock.
+ exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */);
} else if (!mHideAnimationRun) {
if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");
mHideAnimationRun = true;
@@ -2539,15 +2529,6 @@
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
- if (mustNotUnlockCurrentUser()) {
- // In split system user mode, we never unlock system user. The end user has to
- // switch to another user.
- // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
- // still completes and makes the screen blank.
- if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
- mKeyguardExitAnimationRunner = null;
- return;
- }
mHiding = true;
if (mShowing && !mOccluded) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index 16817ed..b92499e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -26,10 +26,10 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -37,16 +37,20 @@
private static final String TAG = WorkLockActivityController.class.getSimpleName();
private final Context mContext;
+ private final UserTracker mUserTracker;
private final IActivityTaskManager mIatm;
- public WorkLockActivityController(Context context) {
- this(context, TaskStackChangeListeners.getInstance(), ActivityTaskManager.getService());
+ public WorkLockActivityController(Context context, UserTracker userTracker) {
+ this(context, userTracker, TaskStackChangeListeners.getInstance(),
+ ActivityTaskManager.getService());
}
@VisibleForTesting
WorkLockActivityController(
- Context context, TaskStackChangeListeners tscl, IActivityTaskManager iAtm) {
+ Context context, UserTracker userTracker, TaskStackChangeListeners tscl,
+ IActivityTaskManager iAtm) {
mContext = context;
+ mUserTracker = userTracker;
mIatm = iAtm;
tscl.registerTaskStackListener(mLockListener);
@@ -65,7 +69,8 @@
options.setLaunchTaskId(info.taskId);
options.setTaskOverlay(true, false /* canResume */);
- final int result = startActivityAsUser(intent, options.toBundle(), UserHandle.USER_CURRENT);
+ final int result = startActivityAsUser(intent, options.toBundle(),
+ mUserTracker.getUserId());
if (ActivityManager.isStartResultSuccessful(result)) {
// OK
} else {
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 ea5b4f4..76c2430 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
@@ -25,10 +25,12 @@
object BuiltInKeyguardQuickAffordanceKeys {
// Please keep alphabetical order of const names to simplify future maintenance.
const val CAMERA = "camera"
+ const val CREATE_NOTE = "create_note"
const val DO_NOT_DISTURB = "do_not_disturb"
const val FLASHLIGHT = "flashlight"
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
+ const val VIDEO_CAMERA = "video_camera"
// Please keep alphabetical order of const names to simplify future maintenance.
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index dbc376e..f6e6d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -26,6 +26,7 @@
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.statusbar.StatusBarState
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -46,7 +47,7 @@
get() = context.getString(R.string.accessibility_camera_button)
override val pickerIconResourceId: Int
- get() = com.android.internal.R.drawable.perm_group_camera
+ get() = R.drawable.ic_camera
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
get() =
@@ -54,12 +55,20 @@
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon =
Icon.Resource(
- com.android.internal.R.drawable.perm_group_camera,
+ R.drawable.ic_camera,
ContentDescription.Resource(R.string.accessibility_camera_button)
)
)
)
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return if (isLaunchable()) {
+ super.getPickerScreenState()
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+ }
+
override fun onTriggered(
expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
@@ -68,4 +77,8 @@
.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
+
+ private fun isLaunchable(): Boolean {
+ return cameraGestureHelper.get().canCameraGestureBeLaunched(StatusBarState.KEYGUARD)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index ed1ff32..be73f85 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -128,7 +128,7 @@
awaitClose { controller.removeCallback(callback) }
},
secureSettings
- .observerFlow(Settings.Secure.ZEN_DURATION)
+ .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
.onStart { emit(Unit) }
.map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
.flowOn(backgroundDispatcher)
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 71d01eb..a1cce5c 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
@@ -39,6 +39,7 @@
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
camera: CameraQuickAffordanceConfig,
+ videoCamera: VideoCameraQuickAffordanceConfig,
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
camera,
@@ -47,6 +48,7 @@
home,
quickAccessWallet,
qrCodeScanner,
+ videoCamera,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 680c06b..4ba2eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -100,9 +100,9 @@
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
return when {
- !walletController.isWalletEnabled ->
+ !walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- walletController.walletClient.tileIcon == null || queryCards().isEmpty() -> {
+ !walletController.isWalletEnabled || queryCards().isEmpty() -> {
val componentName =
walletController.walletClient.createWalletSettingsIntent().toComponentName()
val actionText =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
new file mode 100644
index 0000000..d9ec3b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraIntents
+import com.android.systemui.camera.CameraIntentsWrapper
+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.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class VideoCameraQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val cameraIntents: CameraIntentsWrapper,
+ private val activityIntentHelper: ActivityIntentHelper,
+ private val userTracker: UserTracker,
+) : KeyguardQuickAffordanceConfig {
+
+ private val intent: Intent by lazy {
+ cameraIntents.getVideoCameraIntent().apply {
+ putExtra(
+ CameraIntents.EXTRA_LAUNCH_SOURCE,
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE,
+ )
+ }
+ }
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.VIDEO_CAMERA
+
+ override val pickerName: String
+ get() = context.getString(R.string.video_camera)
+
+ override val pickerIconResourceId: Int
+ get() = R.drawable.ic_videocam
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+ get() =
+ flowOf(
+ if (isLaunchable()) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
+ R.drawable.ic_videocam,
+ ContentDescription.Resource(R.string.video_camera)
+ )
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ )
+
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return if (isLaunchable()) {
+ super.getPickerScreenState()
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = intent,
+ canShowWhileLocked = false,
+ )
+ }
+
+ private fun isLaunchable(): Boolean {
+ return activityIntentHelper.getTargetActivityInfo(
+ intent,
+ userTracker.userId,
+ true,
+ ) != null
+ }
+}
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
new file mode 100644
index 0000000..08edbc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.hardware.biometrics.BiometricSourceType
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates state about device entry fingerprint auth mechanism. */
+interface DeviceEntryFingerprintAuthRepository {
+ /** Whether the device entry fingerprint auth is locked out. */
+ val isLockedOut: Flow<Boolean>
+}
+
+/**
+ * Implementation of [DeviceEntryFingerprintAuthRepository] that uses [KeyguardUpdateMonitor] as the
+ * source of truth.
+ *
+ * Dependency on [KeyguardUpdateMonitor] will be removed once fingerprint auth state is moved out of
+ * [KeyguardUpdateMonitor]
+ */
+@SysUISingleton
+class DeviceEntryFingerprintAuthRepositoryImpl
+@Inject
+constructor(
+ val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) : DeviceEntryFingerprintAuthRepository {
+
+ override val isLockedOut: Flow<Boolean> = conflatedCallbackFlow {
+ val sendLockoutUpdate =
+ fun() {
+ trySendWithFailureLogging(
+ keyguardUpdateMonitor.isFingerprintLockedOut,
+ TAG,
+ "onLockedOutStateChanged"
+ )
+ }
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ sendLockoutUpdate()
+ }
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ sendLockoutUpdate()
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+
+ companion object {
+ const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
new file mode 100644
index 0000000..d90f328
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import com.android.keyguard.logging.TrustRepositoryLogger
+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.keyguard.shared.model.TrustModel
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+interface TrustRepository {
+ /** Flow representing whether the current user is trusted. */
+ val isCurrentUserTrusted: Flow<Boolean>
+}
+
+@SysUISingleton
+class TrustRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val userRepository: UserRepository,
+ private val trustManager: TrustManager,
+ private val logger: TrustRepositoryLogger,
+) : TrustRepository {
+ private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()
+
+ private val trust =
+ conflatedCallbackFlow {
+ val callback =
+ object : TrustManager.TrustListener {
+ override fun onTrustChanged(
+ enabled: Boolean,
+ newlyUnlocked: Boolean,
+ userId: Int,
+ flags: Int,
+ grantMsgs: List<String>?
+ ) {
+ logger.onTrustChanged(enabled, newlyUnlocked, userId, flags, grantMsgs)
+ trySendWithFailureLogging(
+ TrustModel(enabled, userId),
+ TrustRepositoryLogger.TAG,
+ "onTrustChanged"
+ )
+ }
+
+ override fun onTrustError(message: CharSequence?) = Unit
+
+ override fun onTrustManagedChanged(enabled: Boolean, userId: Int) = Unit
+ }
+ trustManager.registerTrustListener(callback)
+ logger.trustListenerRegistered()
+ awaitClose {
+ logger.trustListenerUnregistered()
+ trustManager.unregisterTrustListener(callback)
+ }
+ }
+ .onEach {
+ latestTrustModelForUser[it.userId] = it
+ logger.trustModelEmitted(it)
+ }
+ .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
+
+ override val isCurrentUserTrusted: Flow<Boolean>
+ get() =
+ combine(trust, userRepository.selectedUserInfo, ::Pair)
+ .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+ .distinctUntilChanged()
+ .onEach { logger.isCurrentUserTrusted(it) }
+ .onStart { emit(false) }
+}
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 7134ec0..81a5828 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
@@ -31,8 +31,10 @@
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@SysUISingleton
@@ -87,6 +89,9 @@
private fun listenForDreamingToOccluded() {
scope.launch {
keyguardInteractor.isDreaming
+ // Add a slight delay, as dreaming and occluded events will arrive with a small gap
+ // in time. This prevents a transition to OCCLUSION happening prematurely.
+ .onEach { delay(50) }
.sample(
combine(
keyguardInteractor.isKeyguardOccluded,
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 9203a9b..14f918d 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
@@ -50,10 +50,9 @@
private fun listenForGoneToDreaming() {
scope.launch {
keyguardInteractor.isAbleToDream
- .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
- .collect { pair ->
- val (isAbleToDream, keyguardState) = pair
- if (isAbleToDream && keyguardState == KeyguardState.GONE) {
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (isAbleToDream, lastStartedStep) ->
+ if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
@@ -72,15 +71,15 @@
keyguardInteractor.wakefulnessModel
.sample(
combine(
- keyguardTransitionInteractor.finishedKeyguardState,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
keyguardInteractor.isAodAvailable,
::Pair
),
::toTriple
)
- .collect { (wakefulnessState, keyguardState, isAodAvailable) ->
+ .collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
if (
- keyguardState == KeyguardState.GONE &&
+ lastStartedStep.to == KeyguardState.GONE &&
wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
) {
keyguardTransitionRepository.startTransition(
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 c219380..9ddc575 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
@@ -363,6 +363,10 @@
name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED,
value = featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
),
+ KeyguardPickerFlag(
+ name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW,
+ value = featureFlags.isEnabled(Flags.WALLPAPER_FULLSCREEN_PREVIEW),
+ ),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index a92540d..6679b22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -20,8 +20,6 @@
import android.hardware.biometrics.BiometricSourceType
import android.os.Handler
import android.os.Trace
-import android.os.UserHandle
-import android.os.UserManager
import android.view.View
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -134,12 +132,6 @@
return
}
- val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser()
- if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
- // In split system user mode, we never unlock system user.
- return
- }
-
Trace.beginSection("KeyguardBouncer#show")
repository.setPrimaryScrimmed(isScrimmed)
if (isScrimmed) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
new file mode 100644
index 0000000..4fd14b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.systemui.keyguard.shared.model
+
+/** Represents the trust state */
+data class TrustModel(
+ /** If true, the system believes the environment to be trusted. */
+ val isTrusted: Boolean,
+ /** The user, for which the trust changed. */
+ val userId: Int,
+)
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 afbd8ed..e3c44d1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -191,15 +191,12 @@
false /* systrace */);
}
- /**
- * Provides a logging buffer for logs related to swiping away the status bar while in immersive
- * mode. See {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
- */
+ /** Provides a logging buffer for logs related to swipe up gestures. */
@Provides
@SysUISingleton
- @SwipeStatusBarAwayLog
- public static LogBuffer provideSwipeAwayGestureLogBuffer(LogBufferFactory factory) {
- return factory.create("SwipeStatusBarAwayLog", 30);
+ @SwipeUpLog
+ public static LogBuffer provideSwipeUpLogBuffer(LogBufferFactory factory) {
+ return factory.create("SwipeUpLog", 30);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
rename to packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
index 4c276e2..d58b538 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
@@ -27,10 +27,10 @@
/**
* A {@link LogBuffer} for
- * {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ * {@link com.android.systemui.statusbar.gesture.SwipeUpGestureLogger}.
*/
@Qualifier
@Documented
@Retention(RUNTIME)
-public @interface SwipeStatusBarAwayLog {
+public @interface SwipeUpLog {
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 93be6a7..5c65c8b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -162,8 +162,8 @@
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
UI_MODE_NIGHT_YES
)
- colorScheme.accent1[2]
- else colorScheme.accent1[3]
+ colorScheme.accent1.s100
+ else colorScheme.accent1.s200
},
{ seamlessColor: Int ->
val accentColorList = ColorStateList.valueOf(seamlessColor)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
index 82abf9b..2a8362b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
@@ -19,28 +19,28 @@
import com.android.systemui.monet.ColorScheme
/** Returns the surface color for media controls based on the scheme. */
-internal fun surfaceFromScheme(scheme: ColorScheme) = scheme.accent2[9] // A2-800
+internal fun surfaceFromScheme(scheme: ColorScheme) = scheme.accent2.s800 // A2-800
/** Returns the primary accent color for media controls based on the scheme. */
-internal fun accentPrimaryFromScheme(scheme: ColorScheme) = scheme.accent1[2] // A1-100
+internal fun accentPrimaryFromScheme(scheme: ColorScheme) = scheme.accent1.s100 // A1-100
/** Returns the secondary accent color for media controls based on the scheme. */
-internal fun accentSecondaryFromScheme(scheme: ColorScheme) = scheme.accent1[3] // A1-200
+internal fun accentSecondaryFromScheme(scheme: ColorScheme) = scheme.accent1.s200 // A1-200
/** Returns the primary text color for media controls based on the scheme. */
-internal fun textPrimaryFromScheme(scheme: ColorScheme) = scheme.neutral1[1] // N1-50
+internal fun textPrimaryFromScheme(scheme: ColorScheme) = scheme.neutral1.s50 // N1-50
/** Returns the inverse of the primary text color for media controls based on the scheme. */
-internal fun textPrimaryInverseFromScheme(scheme: ColorScheme) = scheme.neutral1[10] // N1-900
+internal fun textPrimaryInverseFromScheme(scheme: ColorScheme) = scheme.neutral1.s900 // N1-900
/** Returns the secondary text color for media controls based on the scheme. */
-internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2[3] // N2-200
+internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2.s200 // N2-200
/** Returns the tertiary text color for media controls based on the scheme. */
-internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2[5] // N2-400
+internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2.s400 // N2-400
/** Returns the color for the start of the background gradient based on the scheme. */
-internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2[8] // A2-700
+internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2.s700 // A2-700
/** Returns the color for the end of the background gradient based on the scheme. */
-internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1[8] // A1-700
+internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1.s700 // A1-700
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 5b137e9..9cf672b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -493,20 +493,20 @@
ColorScheme mCurrentColorScheme = new ColorScheme(wallpaperColors,
isDarkTheme);
if (isDarkTheme) {
- mColorItemContent = mCurrentColorScheme.getAccent1().get(2); // A1-100
- mColorSeekbarProgress = mCurrentColorScheme.getAccent2().get(7); // A2-600
- mColorButtonBackground = mCurrentColorScheme.getAccent1().get(4); // A1-300
- mColorItemBackground = mCurrentColorScheme.getNeutral2().get(9); // N2-800
- mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().get(9); // A2-800
- mColorPositiveButtonText = mCurrentColorScheme.getAccent2().get(9); // A2-800
- mColorDialogBackground = mCurrentColorScheme.getNeutral1().get(10); // N1-900
+ mColorItemContent = mCurrentColorScheme.getAccent1().getS100(); // A1-100
+ mColorSeekbarProgress = mCurrentColorScheme.getAccent2().getS600(); // A2-600
+ mColorButtonBackground = mCurrentColorScheme.getAccent1().getS300(); // A1-300
+ mColorItemBackground = mCurrentColorScheme.getNeutral2().getS800(); // N2-800
+ mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().getS800(); // A2-800
+ mColorPositiveButtonText = mCurrentColorScheme.getAccent2().getS800(); // A2-800
+ mColorDialogBackground = mCurrentColorScheme.getNeutral1().getS900(); // N1-900
} else {
- mColorItemContent = mCurrentColorScheme.getAccent1().get(9); // A1-800
- mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(4); // A1-300
- mColorButtonBackground = mCurrentColorScheme.getAccent1().get(7); // A1-600
- mColorItemBackground = mCurrentColorScheme.getAccent2().get(1); // A2-50
- mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().get(2); // A1-100
- mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().get(1); // N1-50
+ mColorItemContent = mCurrentColorScheme.getAccent1().getS800(); // A1-800
+ mColorSeekbarProgress = mCurrentColorScheme.getAccent1().getS300(); // A1-300
+ mColorButtonBackground = mCurrentColorScheme.getAccent1().getS600(); // A1-600
+ mColorItemBackground = mCurrentColorScheme.getAccent2().getS50(); // A2-50
+ mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().getS100(); // A1-100
+ mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().getS50(); // N1-50
mColorDialogBackground = mCurrentColorScheme.getBackgroundColor();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 8a565fa..60504e4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -30,4 +30,8 @@
/** Check whether the flag for the receiver success state is enabled. */
fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
+
+ /** True if the media transfer chip can be dismissed via a gesture. */
+ fun isMediaTttDismissGestureEnabled(): Boolean =
+ featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 889147b..6884370 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -31,7 +31,6 @@
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.CachingIconView
-import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.ui.binder.TintedIconViewBinder
@@ -78,6 +77,7 @@
private val viewUtil: ViewUtil,
wakeLockBuilder: WakeLock.Builder,
systemClock: SystemClock,
+ private val rippleController: MediaTttReceiverRippleController,
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
context,
logger,
@@ -114,9 +114,6 @@
}
}
- private var maxRippleWidth: Float = 0f
- private var maxRippleHeight: Float = 0f
-
private fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
routeInfo: MediaRoute2Info,
@@ -206,36 +203,40 @@
override fun animateViewIn(view: ViewGroup) {
val appIconView = view.getAppIconView()
- appIconView.animate()
- .translationYBy(-1 * getTranslationAmount().toFloat())
- .setDuration(ICON_TRANSLATION_ANIM_DURATION)
- .start()
- appIconView.animate()
- .alpha(1f)
- .setDuration(ICON_ALPHA_ANIM_DURATION)
- .start()
+ val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
+ val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
+ animateViewTranslationAndFade(appIconView, -1 * getTranslationAmount(), 1f)
+ animateViewTranslationAndFade(iconRippleView, -1 * getTranslationAmount(), 1f)
// Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
appIconView.postOnAnimation { view.requestAccessibilityFocus() }
- expandRipple(view.requireViewById(R.id.ripple))
+ rippleController.expandToInProgressState(rippleView, iconRippleView)
}
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val appIconView = view.getAppIconView()
- appIconView.animate()
- .translationYBy(getTranslationAmount().toFloat())
- .setDuration(ICON_TRANSLATION_ANIM_DURATION)
- .start()
- appIconView.animate()
- .alpha(0f)
- .setDuration(ICON_ALPHA_ANIM_DURATION)
- .start()
-
+ val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
- expandRippleToFull(rippleView, onAnimationEnd)
+ rippleController.expandToSuccessState(rippleView, onAnimationEnd)
+ animateViewTranslationAndFade(
+ iconRippleView,
+ -1 * getTranslationAmount(),
+ 0f,
+ translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
+ alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
+ )
+ animateViewTranslationAndFade(
+ appIconView,
+ -1 * getTranslationAmount(),
+ 0f,
+ translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
+ alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
+ )
} else {
- rippleView.collapseRipple(onAnimationEnd)
+ rippleController.collapseRipple(rippleView, onAnimationEnd)
+ animateViewTranslationAndFade(iconRippleView, getTranslationAmount(), 0f)
+ animateViewTranslationAndFade(appIconView, getTranslationAmount(), 0f)
}
}
@@ -245,74 +246,41 @@
viewUtil.setRectToViewWindowLocation(view.getAppIconView(), outRect)
}
+ /** Animation of view translation and fading. */
+ private fun animateViewTranslationAndFade(
+ view: View,
+ translationYBy: Float,
+ alphaEndValue: Float,
+ translationDuration: Long = ICON_TRANSLATION_ANIM_DURATION,
+ alphaDuration: Long = ICON_ALPHA_ANIM_DURATION,
+ ) {
+ view.animate()
+ .translationYBy(translationYBy)
+ .setDuration(translationDuration)
+ .start()
+ view.animate()
+ .alpha(alphaEndValue)
+ .setDuration(alphaDuration)
+ .start()
+ }
+
/** Returns the amount that the chip will be translated by in its intro animation. */
- private fun getTranslationAmount(): Int {
- return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
- }
-
- private fun expandRipple(rippleView: ReceiverChipRippleView) {
- if (rippleView.rippleInProgress()) {
- // Skip if ripple is still playing
- return
- }
-
- // In case the device orientation changes, we need to reset the layout.
- rippleView.addOnLayoutChangeListener (
- View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
- if (v == null) return@OnLayoutChangeListener
-
- val layoutChangedRippleView = v as ReceiverChipRippleView
- layoutRipple(layoutChangedRippleView)
- layoutChangedRippleView.invalidate()
- }
- )
- rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
- override fun onViewDetachedFromWindow(view: View?) {}
-
- override fun onViewAttachedToWindow(view: View?) {
- if (view == null) {
- return
- }
- val attachedRippleView = view as ReceiverChipRippleView
- layoutRipple(attachedRippleView)
- attachedRippleView.expandRipple()
- attachedRippleView.removeOnAttachStateChangeListener(this)
- }
- })
- }
-
- private fun layoutRipple(rippleView: ReceiverChipRippleView, isFullScreen: Boolean = false) {
- val windowBounds = windowManager.currentWindowMetrics.bounds
- val height = windowBounds.height().toFloat()
- val width = windowBounds.width().toFloat()
-
- if (isFullScreen) {
- maxRippleHeight = height * 2f
- maxRippleWidth = width * 2f
- } else {
- maxRippleHeight = height / 2f
- maxRippleWidth = width / 2f
- }
- rippleView.setMaxSize(maxRippleWidth, maxRippleHeight)
- // Center the ripple on the bottom of the screen in the middle.
- rippleView.setCenter(width * 0.5f, height)
- val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
- rippleView.setColor(color, 70)
+ private fun getTranslationAmount(): Float {
+ return rippleController.getRippleSize() * 0.5f -
+ rippleController.getReceiverIconSize()
}
private fun View.getAppIconView(): CachingIconView {
return this.requireViewById(R.id.app_icon)
}
- private fun expandRippleToFull(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable?) {
- layoutRipple(rippleView, true)
- rippleView.expandToFull(maxRippleHeight, onAnimationEnd)
+ companion object {
+ private const val ICON_TRANSLATION_ANIM_DURATION = 500L
+ private const val ICON_TRANSLATION_SUCCEEDED_DURATION = 167L
+ private val ICON_ALPHA_ANIM_DURATION = 5.frames
}
}
-val ICON_TRANSLATION_ANIM_DURATION = 30.frames
-val ICON_ALPHA_ANIM_DURATION = 5.frames
-
data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
new file mode 100644
index 0000000..5013802
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.systemui.media.taptotransfer.receiver
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.view.View
+import android.view.WindowManager
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * A controller responsible for the animation of the ripples shown in media tap-to-transfer on the
+ * receiving device.
+ */
+class MediaTttReceiverRippleController
+@Inject
+constructor(
+ private val context: Context,
+ private val windowManager: WindowManager,
+) {
+
+ private var maxRippleWidth: Float = 0f
+ private var maxRippleHeight: Float = 0f
+
+ /** Expands the icon and main ripple to in-progress state */
+ fun expandToInProgressState(
+ mainRippleView: ReceiverChipRippleView,
+ iconRippleView: ReceiverChipRippleView,
+ ) {
+ expandRipple(mainRippleView, isIconRipple = false)
+ expandRipple(iconRippleView, isIconRipple = true)
+ }
+
+ private fun expandRipple(rippleView: ReceiverChipRippleView, isIconRipple: Boolean) {
+ if (rippleView.rippleInProgress()) {
+ // Skip if ripple is still playing
+ return
+ }
+
+ // In case the device orientation changes, we need to reset the layout.
+ rippleView.addOnLayoutChangeListener(
+ View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+ if (v == null) return@OnLayoutChangeListener
+
+ val layoutChangedRippleView = v as ReceiverChipRippleView
+ if (isIconRipple) {
+ layoutIconRipple(layoutChangedRippleView)
+ } else {
+ layoutRipple(layoutChangedRippleView)
+ }
+ layoutChangedRippleView.invalidate()
+ }
+ )
+ rippleView.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewDetachedFromWindow(view: View?) {}
+
+ override fun onViewAttachedToWindow(view: View?) {
+ if (view == null) {
+ return
+ }
+ val attachedRippleView = view as ReceiverChipRippleView
+ if (isIconRipple) {
+ layoutIconRipple(attachedRippleView)
+ } else {
+ layoutRipple(attachedRippleView)
+ }
+ attachedRippleView.expandRipple()
+ attachedRippleView.removeOnAttachStateChangeListener(this)
+ }
+ }
+ )
+ }
+
+ /** Expands the ripple to cover the screen. */
+ fun expandToSuccessState(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable?) {
+ layoutRipple(rippleView, isFullScreen = true)
+ rippleView.expandToFull(maxRippleHeight, onAnimationEnd)
+ }
+
+ /** Collapses the ripple. */
+ fun collapseRipple(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable? = null) {
+ rippleView.collapseRipple(onAnimationEnd)
+ }
+
+ private fun layoutRipple(rippleView: ReceiverChipRippleView, isFullScreen: Boolean = false) {
+ val windowBounds = windowManager.currentWindowMetrics.bounds
+ val height = windowBounds.height().toFloat()
+ val width = windowBounds.width().toFloat()
+
+ if (isFullScreen) {
+ maxRippleHeight = height * 2f
+ maxRippleWidth = width * 2f
+ } else {
+ maxRippleHeight = getRippleSize()
+ maxRippleWidth = getRippleSize()
+ }
+ rippleView.setMaxSize(maxRippleWidth, maxRippleHeight)
+ // Center the ripple on the bottom of the screen in the middle.
+ rippleView.setCenter(width * 0.5f, height)
+ rippleView.setColor(getRippleColor(), RIPPLE_OPACITY)
+ }
+
+ private fun layoutIconRipple(iconRippleView: ReceiverChipRippleView) {
+ val windowBounds = windowManager.currentWindowMetrics.bounds
+ val height = windowBounds.height().toFloat()
+ val width = windowBounds.width().toFloat()
+ val radius = getReceiverIconSize().toFloat()
+
+ iconRippleView.setMaxSize(radius * 0.8f, radius * 0.8f)
+ iconRippleView.setCenter(
+ width * 0.5f,
+ height - getReceiverIconSize() * 0.5f - getReceiverIconBottomMargin()
+ )
+ iconRippleView.setColor(getRippleColor(), RIPPLE_OPACITY)
+ }
+
+ private fun getRippleColor(): Int {
+ var colorStateList =
+ ColorStateList.valueOf(
+ Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+ )
+ return colorStateList.withLStar(TONE_PERCENT).defaultColor
+ }
+
+ /** Returns the size of the ripple. */
+ internal fun getRippleSize(): Float {
+ return getReceiverIconSize() * 4f
+ }
+
+ /** Returns the size of the icon of the receiver. */
+ internal fun getReceiverIconSize(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver)
+ }
+
+ /** Return the bottom margin of the icon of the receiver. */
+ internal fun getReceiverIconBottomMargin(): Int {
+ // Adding a margin to make sure ripple behind the icon is not cut by the screen bounds.
+ return context.resources.getDimensionPixelSize(
+ R.dimen.media_ttt_receiver_icon_bottom_margin
+ )
+ }
+
+ companion object {
+ const val RIPPLE_OPACITY = 70
+ const val TONE_PERCENT = 95f
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 87b2528..f8785fc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -33,14 +33,14 @@
private var isStarted: Boolean
init {
- setupShader(RippleShader.RippleShape.ELLIPSE)
+ setupShader(RippleShader.RippleShape.CIRCLE)
setRippleFill(true)
setSparkleStrength(0f)
- duration = 3000L
isStarted = false
}
fun expandRipple(onAnimationEnd: Runnable? = null) {
+ duration = DEFAULT_DURATION
isStarted = true
super.startRipple(onAnimationEnd)
}
@@ -50,6 +50,7 @@
if (!isStarted) {
return // Ignore if ripple is not started yet.
}
+ duration = DEFAULT_DURATION
// Reset all listeners to animator.
animator.removeAllListeners()
animator.addListener(object : AnimatorListenerAdapter() {
@@ -74,6 +75,7 @@
setRippleFill(false)
val startingPercentage = calculateStartingPercentage(newHeight)
+ animator.duration = EXPAND_TO_FULL_DURATION
animator.addUpdateListener { updateListener ->
val now = updateListener.currentPlayTime
val progress = updateListener.animatedValue as Float
@@ -100,4 +102,9 @@
val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
return 1 - remainingPercentage
}
+
+ companion object {
+ const val DEFAULT_DURATION = 333L
+ const val EXPAND_TO_FULL_DURATION = 1000L
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 935f38d..902a10a0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -30,6 +30,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
@@ -54,6 +55,7 @@
private var displayedState: ChipStateSender? = null
// A map to store current chip state per id.
+ // TODO(b/265455911): Log whenever we add or remove from the store.
private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
private val commandQueueCallbacks =
@@ -102,10 +104,9 @@
}
uiEventLogger.logSenderStateChange(chipState)
- stateMap.put(routeInfo.id, chipState)
if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
// No need to store the state since it is the default state
- stateMap.remove(routeInfo.id)
+ removeIdFromStore(routeInfo.id)
// Return early if we're not displaying a chip anyway
val currentDisplayedState = displayedState ?: return
@@ -126,7 +127,9 @@
displayedState = null
chipbarCoordinator.removeView(routeInfo.id, removalReason)
} else {
+ stateMap[routeInfo.id] = chipState
displayedState = chipState
+ chipbarCoordinator.registerListener(displayListener)
chipbarCoordinator.displayView(
createChipbarInfo(
chipState,
@@ -135,7 +138,7 @@
context,
logger,
)
- ) { stateMap.remove(routeInfo.id) }
+ )
}
}
@@ -182,6 +185,7 @@
}
},
vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+ allowSwipeToDismiss = true,
windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
timeoutMs = chipStateSender.timeout,
@@ -225,4 +229,14 @@
onClickListener,
)
}
+
+ private val displayListener =
+ TemporaryViewDisplayController.Listener { id -> removeIdFromStore(id) }
+
+ private fun removeIdFromStore(id: String) {
+ stateMap.remove(id)
+ if (stateMap.isEmpty()) {
+ chipbarCoordinator.unregisterListener(displayListener)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index a92203c..1121e160 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -297,7 +297,7 @@
private void updateAssistantAvailability() {
boolean assistantAvailableForUser = mAssistManagerLazy.get()
- .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+ .getAssistInfoForUser(mUserTracker.getUserId()) != null;
boolean longPressDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault);
mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e0aa6a8..32dee92 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -77,7 +77,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.telecom.TelecomManager;
import android.text.TextUtils;
@@ -745,6 +744,7 @@
setWindowVisible(isNavBarWindowVisible());
mView.setBehavior(mBehavior);
setNavBarMode(mNavBarMode);
+ repositionNavigationBar(mCurrentRotation);
mView.setUpdateActiveTouchRegionsCallback(
() -> mOverviewProxyService.onActiveNavBarRegionChanges(
getButtonLocations(
@@ -1492,7 +1492,7 @@
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index e64c188..046a284 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -24,7 +24,6 @@
import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -63,8 +62,8 @@
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -87,6 +86,7 @@
private final Handler mHandler;
private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
private FeatureFlags mFeatureFlags;
+ private final SecureSettings mSecureSettings;
private final DisplayManager mDisplayManager;
private final TaskbarDelegate mTaskbarDelegate;
private int mNavMode;
@@ -118,11 +118,13 @@
TaskStackChangeListeners taskStackChangeListeners,
Optional<Pip> pipOptional,
Optional<BackAnimation> backAnimation,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ SecureSettings secureSettings) {
mContext = context;
mHandler = mainHandler;
mNavigationBarComponentFactory = navigationBarComponentFactory;
mFeatureFlags = featureFlags;
+ mSecureSettings = secureSettings;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
commandQueue.addCallback(this);
configurationController.addCallback(this);
@@ -192,8 +194,7 @@
}
private void updateAccessibilityButtonModeIfNeeded() {
- ContentResolver contentResolver = mContext.getContentResolver();
- final int mode = Settings.Secure.getIntForUser(contentResolver,
+ final int mode = mSecureSettings.getIntForUser(
Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
@@ -207,14 +208,14 @@
// force update to ACCESSIBILITY_BUTTON_MODE_GESTURE.
if (QuickStepContract.isGesturalMode(mNavMode)
&& mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) {
- Settings.Secure.putIntForUser(contentResolver,
+ mSecureSettings.putIntForUser(
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE,
UserHandle.USER_CURRENT);
// ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to
// force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR.
} else if (!QuickStepContract.isGesturalMode(mNavMode)
&& mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) {
- Settings.Secure.putIntForUser(contentResolver,
+ mSecureSettings.putIntForUser(
Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
index 26e3f49..4b10d69 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
@@ -72,6 +72,9 @@
companion object {
// TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead.
const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
+
+ // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
+ const val NOTE_ROLE = "android.app.role.NOTES"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 8bdf319..22ce121 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -18,11 +18,13 @@
import android.app.Activity
import android.app.KeyguardManager
+import android.app.role.RoleManager
import android.content.Context
import android.os.UserManager
import androidx.core.content.getSystemService
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
import dagger.Binds
@@ -33,20 +35,25 @@
import java.util.Optional
/** Compose all dependencies required by Note Task feature. */
-@Module
+@Module(includes = [NoteTaskQuickAffordanceModule::class])
internal interface NoteTaskModule {
@[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
- fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity?
+ fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
@[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
- fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity?
+ fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
companion object {
@[Provides NoteTaskEnabledKey]
- fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
- return featureFlags.isEnabled(Flags.NOTE_TASKS)
+ fun provideIsNoteTaskEnabled(
+ featureFlags: FeatureFlags,
+ roleManager: RoleManager,
+ ): Boolean {
+ val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.NOTE_ROLE)
+ val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
+ return isRoleAvailable && isFeatureEnabled
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
new file mode 100644
index 0000000..cfbaa48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.systemui.notetask.quickaffordance
+
+import android.content.Context
+import com.android.systemui.R
+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.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskEnabledKey
+import javax.inject.Inject
+import kotlinx.coroutines.flow.flowOf
+
+internal class NoteTaskQuickAffordanceConfig
+@Inject
+constructor(
+ context: Context,
+ private val noteTaskController: NoteTaskController,
+ @NoteTaskEnabledKey private val isEnabled: Boolean,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE
+
+ override val pickerName: String = context.getString(R.string.note_task_button_label)
+
+ override val pickerIconResourceId = R.drawable.ic_note_task_shortcut_keyguard
+
+ override val lockScreenState = flowOf(getLockScreenState())
+
+ // TODO(b/265949213)
+ private fun getLockScreenState() =
+ if (isEnabled) {
+ val icon = Icon.Resource(pickerIconResourceId, ContentDescription.Loaded(pickerName))
+ LockScreenState.Visible(icon)
+ } else {
+ LockScreenState.Hidden
+ }
+
+ override suspend fun getPickerScreenState() =
+ if (isEnabled) {
+ PickerScreenState.Default()
+ } else {
+ PickerScreenState.UnavailableOnDevice
+ }
+
+ override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
+ noteTaskController.showNoteTask()
+ return OnTriggeredResult.Handled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
new file mode 100644
index 0000000..7cb932a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.systemui.notetask.quickaffordance
+
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+internal interface NoteTaskQuickAffordanceModule {
+
+ @[Binds IntoSet]
+ fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
index f6a623e..6ab0da6 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -46,7 +46,7 @@
id = SHORTCUT_ID,
shortLabel = getString(R.string.note_task_button_label),
intent = LaunchNoteTaskActivity.newIntent(context = this),
- iconResource = R.drawable.ic_note_task_button,
+ iconResource = R.drawable.ic_note_task_shortcut_widget,
)
setResult(Activity.RESULT_OK, intent)
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 3587c4d..8d8903d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -68,6 +68,7 @@
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.NotificationChannels;
@@ -177,7 +178,7 @@
private final BroadcastSender mBroadcastSender;
private final UiEventLogger mUiEventLogger;
private GlobalSettings mGlobalSettings;
-
+ private final UserTracker mUserTracker;
private final Lazy<BatteryController> mBatteryControllerLazy;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -187,7 +188,7 @@
public PowerNotificationWarnings(Context context, ActivityStarter activityStarter,
BroadcastSender broadcastSender, Lazy<BatteryController> batteryControllerLazy,
DialogLaunchAnimator dialogLaunchAnimator, UiEventLogger uiEventLogger,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings, UserTracker userTracker) {
mContext = context;
mNoMan = mContext.getSystemService(NotificationManager.class);
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -200,6 +201,7 @@
mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog);
mUiEventLogger = uiEventLogger;
mGlobalSettings = globalSettings;
+ mUserTracker = userTracker;
}
@Override
@@ -699,7 +701,7 @@
Secure.putIntForUser(
resolver,
Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
- 1, UserHandle.USER_CURRENT);
+ 1, mUserTracker.getUserId());
});
} else {
d.setTitle(R.string.battery_saver_confirmation_title);
@@ -850,7 +852,8 @@
logEvent(BatteryWarningEvents
.LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_SETTINGS);
dismissLowBatteryNotification();
- mContext.startActivityAsUser(mOpenBatterySaverSettings, UserHandle.CURRENT);
+ mContext.startActivityAsUser(mOpenBatterySaverSettings,
+ mUserTracker.getUserHandle());
} else if (action.equals(ACTION_START_SAVER)) {
logEvent(BatteryWarningEvents
.LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index 39d081d..36dc743 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -83,8 +83,7 @@
if (mListeners.size() > 0) {
mSecureSettings.unregisterContentObserver(mContentObserver);
mSecureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
false, mContentObserver, newUser);
}
}
@@ -100,8 +99,7 @@
mListeners.add(listener);
if (mListeners.size() == 1) {
mSecureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
false, mContentObserver, mUserTracker.getUserId());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index c335a6d..5ea1c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -59,11 +59,11 @@
}
@Override
- public void showRecentApps(boolean triggeredFromAltTab, boolean forward) {
+ public void showRecentApps(boolean triggeredFromAltTab) {
IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
if (overviewProxy != null) {
try {
- overviewProxy.onOverviewShown(triggeredFromAltTab, forward);
+ overviewProxy.onOverviewShown(triggeredFromAltTab);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send overview show event to launcher.", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4d005be..1151475 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -21,7 +21,6 @@
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
@@ -44,8 +43,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Insets;
-import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Binder;
@@ -77,6 +74,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -94,7 +92,6 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -311,7 +308,7 @@
intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
});
}
@@ -322,18 +319,8 @@
}
@Override
- public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
- Insets visibleInsets, Task.TaskKey task) {
- mScreenshotHelper.provideScreenshot(
- screenImageBundle,
- locationInScreen,
- visibleInsets,
- task.id,
- task.userId,
- task.sourceComponent,
- SCREENSHOT_OVERVIEW,
- mHandler,
- null);
+ public void takeScreenshot(ScreenshotRequest request) {
+ mScreenshotHelper.takeScreenshot(request, mHandler, null);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 95d6c18..b041f95 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -65,14 +65,14 @@
}
@Override
- public void showRecentApps(boolean triggeredFromAltTab, boolean forward) {
+ public void showRecentApps(boolean triggeredFromAltTab) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return;
}
- mImpl.showRecentApps(triggeredFromAltTab, forward);
+ mImpl.showRecentApps(triggeredFromAltTab);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
index 010ceda..8848dbb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
@@ -31,7 +31,7 @@
default void preloadRecentApps() {}
default void cancelPreloadRecentApps() {}
- default void showRecentApps(boolean triggeredFromAltTab, boolean forward) {}
+ default void showRecentApps(boolean triggeredFromAltTab) {}
default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {}
default void toggleRecentApps() {}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 5450db9..d64b33b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -49,6 +49,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
+import com.android.systemui.settings.UserTracker;
import com.google.common.util.concurrent.ListenableFuture;
@@ -79,6 +80,7 @@
private final LongScreenshotData mLongScreenshotHolder;
private final ActionIntentExecutor mActionExecutor;
private final FeatureFlags mFeatureFlags;
+ private final UserTracker mUserTracker;
private ImageView mPreview;
private ImageView mTransitionView;
@@ -110,7 +112,7 @@
public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
@Main Executor mainExecutor, @Background Executor bgExecutor,
LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags, UserTracker userTracker) {
mUiEventLogger = uiEventLogger;
mUiExecutor = mainExecutor;
mBackgroundExecutor = bgExecutor;
@@ -118,6 +120,7 @@
mLongScreenshotHolder = longScreenshotHolder;
mActionExecutor = actionExecutor;
mFeatureFlags = featureFlags;
+ mUserTracker = userTracker;
}
@@ -375,7 +378,7 @@
Intent sharingChooserIntent = Intent.createChooser(intent, null)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+ startActivityAsUser(sharingChooserIntent, mUserTracker.getUserHandle());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 95cc0dc..f011aab 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -19,27 +19,26 @@
import android.graphics.Insets
import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
-import java.util.function.Consumer
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import java.util.function.Consumer
+import javax.inject.Inject
/**
* Processes a screenshot request sent from {@link ScreenshotHelper}.
*/
@SysUISingleton
class RequestProcessor @Inject constructor(
- private val capture: ImageCapture,
- private val policy: ScreenshotPolicy,
- private val flags: FeatureFlags,
- /** For the Java Async version, to invoke the callback. */
- @Application private val mainScope: CoroutineScope
+ private val capture: ImageCapture,
+ private val policy: ScreenshotPolicy,
+ private val flags: FeatureFlags,
+ /** For the Java Async version, to invoke the callback. */
+ @Application private val mainScope: CoroutineScope
) {
/**
* Inspects the incoming request, returning a potentially modified request depending on policy.
@@ -58,7 +57,7 @@
// regardless of the managed profile status.
if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
- flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
) {
val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
@@ -66,17 +65,21 @@
result = if (policy.isManagedProfile(info.user.identifier)) {
val image = capture.captureTask(info.taskId)
- ?: error("Task snapshot returned a null Bitmap!")
+ ?: error("Task snapshot returned a null Bitmap!")
// Provide the task snapshot as the screenshot
- ScreenshotRequest(
- TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
- HardwareBitmapBundler.hardwareBitmapToBundle(image),
- info.bounds, Insets.NONE, info.taskId, info.user.identifier, info.component
- )
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
+ .setTopComponent(info.component)
+ .setTaskId(info.taskId)
+ .setUserId(info.user.identifier)
+ .setBitmap(image)
+ .setBoundsOnScreen(info.bounds)
+ .setInsets(Insets.NONE)
+ .build()
} else {
// Create a new request of the same type which includes the top component
- ScreenshotRequest(request.type, request.source, info.component)
+ ScreenshotRequest.Builder(request.type, request.source)
+ .setTopComponent(info.component).build()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b21a485..6d87922 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -416,10 +416,11 @@
}
boolean showFlash = false;
- if (!aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
+ if (screenshotScreenBounds == null
+ || !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
showFlash = true;
visibleInsets = Insets.NONE;
- screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
+ screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
}
mCurrentRequestCallback = requestCallback;
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
@@ -553,6 +554,10 @@
Log.d(TAG, "adding OnComputeInternalInsetsListener");
}
mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setContentView: " + mScreenshotView);
+ }
+ setContentView(mScreenshotView);
}
/**
@@ -634,6 +639,7 @@
// The window is focusable by default
setWindowFocusable(true);
+ mScreenshotView.requestFocus();
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
@@ -691,10 +697,7 @@
mContext.getDrawable(R.drawable.overlay_badge_background), owner));
}
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setContentView: " + mScreenshotView);
- }
- setContentView(mScreenshotView);
+
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 35e9f3e..7b271a8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -54,7 +54,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
@@ -186,8 +186,7 @@
final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
RequestCallback callback = new RequestCallbackImpl(replyTo);
- ScreenshotHelper.ScreenshotRequest request =
- (ScreenshotHelper.ScreenshotRequest) msg.obj;
+ ScreenshotRequest request = (ScreenshotRequest) msg.obj;
handleRequest(request, onSaved, callback);
return true;
@@ -195,7 +194,7 @@
@MainThread
@VisibleForTesting
- void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved,
+ void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
RequestCallback callback) {
// If the storage for this user is locked, we have no place to store
// the screenshot, so skip taking it instead of showing a misleading
@@ -226,7 +225,7 @@
(r) -> dispatchToController(r, onSaved, callback));
}
- private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
+ private void dispatchToController(ScreenshotRequest request,
Consumer<Uri> uriConsumer, RequestCallback callback) {
ComponentName topComponent = request.getTopComponent();
@@ -244,8 +243,7 @@
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
}
- Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
- request.getBitmapBundle());
+ Bitmap screenshot = request.getBitmap();
Rect screenBounds = request.getBoundsInScreen();
Insets insets = request.getInsets();
int taskId = request.getTaskId();
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 1558ac5..287e810 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -62,12 +62,24 @@
fun removeCallback(callback: Callback)
/**
- * Ćallback for notifying of changes.
+ * Callback for notifying of changes.
*/
interface Callback {
/**
+ * Notifies that the current user is being changed.
+ * Override this method to run things while the screen is frozen for the user switch.
+ * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the
+ * screen further. Please be aware that code executed in this callback will lengthen the
+ * user switch duration.
+ */
+ @JvmDefault
+ fun onUserChanging(newUser: Int, userContext: Context) {}
+
+ /**
* Notifies that the current user has changed.
+ * Override this method to run things after the screen is unfrozen for the user switch.
+ * Please see {@link #onUserChanging} if you need to hide jank.
*/
@JvmDefault
fun onUserChanged(newUser: Int, userContext: Context) {}
@@ -78,4 +90,4 @@
@JvmDefault
fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 4dbe099..3a5d0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -16,6 +16,8 @@
package com.android.systemui.settings
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Context
@@ -23,6 +25,7 @@
import android.content.IntentFilter
import android.content.pm.UserInfo
import android.os.Handler
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
@@ -33,6 +36,7 @@
import com.android.systemui.util.Assert
import java.io.PrintWriter
import java.lang.ref.WeakReference
+import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@@ -55,6 +59,7 @@
open class UserTrackerImpl internal constructor(
private val context: Context,
private val userManager: UserManager,
+ private val iActivityManager: IActivityManager,
private val dumpManager: DumpManager,
private val backgroundHandler: Handler
) : UserTracker, Dumpable, BroadcastReceiver() {
@@ -106,7 +111,6 @@
setUserIdInternal(startingUser)
val filter = IntentFilter().apply {
- addAction(Intent.ACTION_USER_SWITCHED)
addAction(Intent.ACTION_USER_INFO_CHANGED)
// These get called when a managed profile goes in or out of quiet mode.
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
@@ -117,14 +121,13 @@
}
context.registerReceiverForAllUsers(this, filter, null /* permission */, backgroundHandler)
+ registerUserSwitchObserver()
+
dumpManager.registerDumpable(TAG, this)
}
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
- Intent.ACTION_USER_SWITCHED -> {
- handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL))
- }
Intent.ACTION_USER_INFO_CHANGED,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
@@ -156,22 +159,43 @@
return ctx to profiles
}
+ private fun registerUserSwitchObserver() {
+ iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
+ override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
+ backgroundHandler.run {
+ handleUserSwitching(newUserId)
+ reply?.sendResult(null)
+ }
+ }
+
+ override fun onUserSwitchComplete(newUserId: Int) {
+ backgroundHandler.run {
+ handleUserSwitchComplete(newUserId)
+ }
+ }
+ }, TAG)
+ }
+
@WorkerThread
- protected open fun handleSwitchUser(newUser: Int) {
+ protected open fun handleUserSwitching(newUserId: Int) {
Assert.isNotMainThread()
- if (newUser == UserHandle.USER_NULL) {
- Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent")
- return
- }
+ Log.i(TAG, "Switching to user $newUserId")
- if (newUser == userId) return
- Log.i(TAG, "Switching to user $newUser")
-
- val (ctx, profiles) = setUserIdInternal(newUser)
-
+ setUserIdInternal(newUserId)
notifySubscribers {
- onUserChanged(newUser, ctx)
- onProfilesChanged(profiles)
+ onUserChanging(newUserId, userContext)
+ }.await()
+ }
+
+ @WorkerThread
+ protected open fun handleUserSwitchComplete(newUserId: Int) {
+ Assert.isNotMainThread()
+ Log.i(TAG, "Switched to user $newUserId")
+
+ setUserIdInternal(newUserId)
+ notifySubscribers {
+ onUserChanged(newUserId, userContext)
+ onProfilesChanged(userProfiles)
}
}
@@ -200,17 +224,25 @@
}
}
- private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) {
+ private inline fun notifySubscribers(
+ crossinline action: UserTracker.Callback.() -> Unit
+ ): CountDownLatch {
val list = synchronized(callbacks) {
callbacks.toList()
}
+ val latch = CountDownLatch(list.size)
+
list.forEach {
if (it.callback.get() != null) {
it.executor.execute {
it.callback.get()?.action()
+ latch.countDown()
}
+ } else {
+ latch.countDown()
}
}
+ return latch
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -257,4 +289,4 @@
fun sameOrEmpty(other: UserTracker.Callback): Boolean {
return callback.get()?.equals(other) ?: true
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 6bd9158..b69786e 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -208,7 +208,7 @@
automatic = Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
- UserHandle.USER_CURRENT);
+ mUserTracker.getUserId());
mAutomatic = automatic != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
index 2f62e44..809fa29 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.settings.dagger;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.content.Context;
import android.os.Handler;
import android.os.UserManager;
@@ -57,11 +58,13 @@
static UserTracker provideUserTracker(
Context context,
UserManager userManager,
+ IActivityManager iActivityManager,
DumpManager dumpManager,
@Background Handler handler
) {
int startingUser = ActivityManager.getCurrentUser();
- UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, dumpManager, handler);
+ UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, iActivityManager,
+ dumpManager, handler);
tracker.initialize(startingUser);
return tracker;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index bf4a768..d11f9da 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2097,7 +2097,17 @@
}
}
- public void expandWithoutQs() {
+ /**
+ * Expand shade so that notifications are visible.
+ * Non-split shade: just expanding shade or collapsing QS when they're expanded.
+ * Split shade: only expanding shade, notifications are always visible
+ *
+ * Called when `adb shell cmd statusbar expand-notifications` is executed.
+ */
+ public void expandShadeToNotifications() {
+ if (mSplitShadeEnabled && (isShadeFullyOpen() || isExpanding())) {
+ return;
+ }
if (isQsExpanded()) {
flingSettings(0 /* velocity */, FLING_COLLAPSE);
} else {
@@ -4186,9 +4196,7 @@
}
private void updateStatusBarIcons() {
- boolean showIconsWhenExpanded =
- (isPanelVisibleBecauseOfHeadsUp() || mIsFullWidth)
- && getExpandedHeight() < getOpeningHeight();
+ boolean showIconsWhenExpanded = getExpandedHeight() < getOpeningHeight();
if (showIconsWhenExpanded && isOnKeyguard()) {
showIconsWhenExpanded = false;
}
@@ -4255,7 +4263,7 @@
&& mHeadsUpAppearanceController.shouldBeVisible()) {
return false;
}
- return !mIsFullWidth || !mShowIconsWhenExpanded;
+ return !mShowIconsWhenExpanded;
}
private void onQsPanelScrollChanged(int scrollY) {
@@ -5538,7 +5546,7 @@
@Override
public void flingTopOverscroll(float velocity, boolean open) {
- // in split shade mode we want to expand/collapse QS only when touch happens within QS
+ // in split shade touches affect QS only when touch happens within QS
if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index db70065..b42bdaa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -19,7 +19,6 @@
import android.hardware.display.AmbientDisplayConfiguration
import android.os.PowerManager
import android.os.SystemClock
-import android.os.UserHandle
import android.provider.Settings
import android.view.GestureDetector
import android.view.MotionEvent
@@ -29,6 +28,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.tuner.TunerService
@@ -54,6 +54,7 @@
private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val statusBarStateController: StatusBarStateController,
private val shadeLogger: ShadeLogger,
+ userTracker: UserTracker,
tunerService: TunerService,
dumpManager: DumpManager
) : GestureDetector.SimpleOnGestureListener(), Dumpable {
@@ -65,10 +66,10 @@
when (key) {
Settings.Secure.DOZE_DOUBLE_TAP_GESTURE ->
doubleTapEnabled = ambientDisplayConfiguration.doubleTapGestureEnabled(
- UserHandle.USER_CURRENT)
+ userTracker.userId)
Settings.Secure.DOZE_TAP_SCREEN_GESTURE ->
singleTapEnabled = ambientDisplayConfiguration.tapGestureEnabled(
- UserHandle.USER_CURRENT)
+ userTracker.userId)
}
}
tunerService.addTunable(tunable,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 7556750..2f25244 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -228,7 +228,7 @@
*/
default void setImeWindowStatus(int displayId, IBinder token, int vis,
@BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
- default void showRecentApps(boolean triggeredFromAltTab, boolean forward) { }
+ default void showRecentApps(boolean triggeredFromAltTab) { }
default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
default void toggleRecentApps() { }
default void toggleSplitScreen() { }
@@ -695,11 +695,11 @@
}
}
- public void showRecentApps(boolean triggeredFromAltTab, boolean forward) {
+ public void showRecentApps(boolean triggeredFromAltTab) {
synchronized (mLock) {
mHandler.removeMessages(MSG_SHOW_RECENT_APPS);
- mHandler.obtainMessage(MSG_SHOW_RECENT_APPS, triggeredFromAltTab ? 1 : 0,
- forward ? 1 : 0, null).sendToTarget();
+ mHandler.obtainMessage(MSG_SHOW_RECENT_APPS, triggeredFromAltTab ? 1 : 0, 0,
+ null).sendToTarget();
}
}
@@ -1407,7 +1407,7 @@
break;
case MSG_SHOW_RECENT_APPS:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showRecentApps(msg.arg1 != 0, msg.arg2 != 0);
+ mCallbacks.get(i).showRecentApps(msg.arg1 != 0);
}
break;
case MSG_HIDE_RECENT_APPS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
index 6115819..5ab3d7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -17,90 +17,25 @@
package com.android.systemui.statusbar.gesture
import android.content.Context
-import android.view.InputEvent
import android.view.MotionEvent
-import android.view.MotionEvent.ACTION_CANCEL
-import android.view.MotionEvent.ACTION_DOWN
-import android.view.MotionEvent.ACTION_MOVE
-import android.view.MotionEvent.ACTION_UP
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.window.StatusBarWindowController
import javax.inject.Inject
-/**
- * A class to detect when a user swipes away the status bar. To be notified when the swipe away
- * gesture is detected, add a callback via [addOnGestureDetectedCallback].
- */
+/** A class to detect when a user swipes away the status bar. */
@SysUISingleton
-open class SwipeStatusBarAwayGestureHandler @Inject constructor(
+class SwipeStatusBarAwayGestureHandler
+@Inject
+constructor(
context: Context,
+ logger: SwipeUpGestureLogger,
private val statusBarWindowController: StatusBarWindowController,
- private val logger: SwipeStatusBarAwayGestureLogger
-) : GenericGestureDetector(SwipeStatusBarAwayGestureHandler::class.simpleName!!) {
-
- private var startY: Float = 0f
- private var startTime: Long = 0L
- private var monitoringCurrentTouch: Boolean = false
-
- private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.system_gestures_start_threshold
- )
-
- override fun onInputEvent(ev: InputEvent) {
- if (ev !is MotionEvent) {
- return
- }
-
- when (ev.actionMasked) {
- ACTION_DOWN -> {
- if (
- // Gesture starts just below the status bar
- ev.y >= statusBarWindowController.statusBarHeight
- && ev.y <= 3 * statusBarWindowController.statusBarHeight
- ) {
- logger.logGestureDetectionStarted(ev.y.toInt())
- startY = ev.y
- startTime = ev.eventTime
- monitoringCurrentTouch = true
- } else {
- monitoringCurrentTouch = false
- }
- }
- ACTION_MOVE -> {
- if (!monitoringCurrentTouch) {
- return
- }
- if (
- // Gesture is up
- ev.y < startY
- // Gesture went far enough
- && (startY - ev.y) >= swipeDistanceThreshold
- // Gesture completed quickly enough
- && (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
- ) {
- monitoringCurrentTouch = false
- logger.logGestureDetected(ev.y.toInt())
- onGestureDetected(ev)
- }
- }
- ACTION_CANCEL, ACTION_UP -> {
- if (monitoringCurrentTouch) {
- logger.logGestureDetectionEndedWithoutTriggering(ev.y.toInt())
- }
- monitoringCurrentTouch = false
- }
- }
- }
-
- override fun startGestureListening() {
- super.startGestureListening()
- logger.logInputListeningStarted()
- }
-
- override fun stopGestureListening() {
- super.stopGestureListening()
- logger.logInputListeningStopped()
+) : SwipeUpGestureHandler(context, logger, loggerTag = LOGGER_TAG) {
+ override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean {
+ // Gesture starts just below the status bar
+ return ev.y >= statusBarWindowController.statusBarHeight &&
+ ev.y <= 3 * statusBarWindowController.statusBarHeight
}
}
-private const val SWIPE_TIMEOUT_MS: Long = 500
+private const val LOGGER_TAG = "SwipeStatusBarAway"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
new file mode 100644
index 0000000..5ecc35c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar.gesture
+
+import android.content.Context
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_CANCEL
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import com.android.systemui.dagger.SysUISingleton
+
+/**
+ * A class to detect a generic "swipe up" gesture. To be notified when the swipe up gesture is
+ * detected, add a callback via [addOnGestureDetectedCallback].
+ */
+@SysUISingleton
+abstract class SwipeUpGestureHandler(
+ context: Context,
+ private val logger: SwipeUpGestureLogger,
+ private val loggerTag: String,
+) : GenericGestureDetector(SwipeUpGestureHandler::class.simpleName!!) {
+
+ private var startY: Float = 0f
+ private var startTime: Long = 0L
+ private var monitoringCurrentTouch: Boolean = false
+
+ private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_start_threshold
+ )
+
+ override fun onInputEvent(ev: InputEvent) {
+ if (ev !is MotionEvent) {
+ return
+ }
+
+ when (ev.actionMasked) {
+ ACTION_DOWN -> {
+ if (
+ startOfGestureIsWithinBounds(ev)
+ ) {
+ logger.logGestureDetectionStarted(loggerTag, ev.y.toInt())
+ startY = ev.y
+ startTime = ev.eventTime
+ monitoringCurrentTouch = true
+ } else {
+ monitoringCurrentTouch = false
+ }
+ }
+ ACTION_MOVE -> {
+ if (!monitoringCurrentTouch) {
+ return
+ }
+ if (
+ // Gesture is up
+ ev.y < startY &&
+ // Gesture went far enough
+ (startY - ev.y) >= swipeDistanceThreshold &&
+ // Gesture completed quickly enough
+ (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
+ ) {
+ monitoringCurrentTouch = false
+ logger.logGestureDetected(loggerTag, ev.y.toInt())
+ onGestureDetected(ev)
+ }
+ }
+ ACTION_CANCEL, ACTION_UP -> {
+ if (monitoringCurrentTouch) {
+ logger.logGestureDetectionEndedWithoutTriggering(loggerTag, ev.y.toInt())
+ }
+ monitoringCurrentTouch = false
+ }
+ }
+ }
+
+ /**
+ * Returns true if the [ACTION_DOWN] event falls within bounds for this specific swipe-up
+ * gesture.
+ *
+ * Implementations must override this method to specify what part(s) of the screen are valid
+ * locations for the swipe up gesture to start at.
+ */
+ abstract fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean
+
+ override fun startGestureListening() {
+ super.startGestureListening()
+ logger.logInputListeningStarted(loggerTag)
+ }
+
+ override fun stopGestureListening() {
+ super.stopGestureListening()
+ logger.logInputListeningStopped(loggerTag)
+ }
+}
+
+private const val SWIPE_TIMEOUT_MS: Long = 500
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
index 9bdff92..9ce6b02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
@@ -16,49 +16,49 @@
package com.android.systemui.statusbar.gesture
-import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.SwipeUpLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
-/** Log messages for [SwipeStatusBarAwayGestureHandler]. */
-class SwipeStatusBarAwayGestureLogger @Inject constructor(
- @SwipeStatusBarAwayLog private val buffer: LogBuffer
+/** Log messages for [SwipeUpGestureHandler]. */
+@SysUISingleton
+class SwipeUpGestureLogger @Inject constructor(
+ @SwipeUpLog private val buffer: LogBuffer,
) {
- fun logGestureDetectionStarted(y: Int) {
+ fun logGestureDetectionStarted(tag: String, y: Int) {
buffer.log(
- TAG,
+ tag,
LogLevel.DEBUG,
{ int1 = y },
{ "Beginning gesture detection. y=$int1" }
)
}
- fun logGestureDetectionEndedWithoutTriggering(y: Int) {
+ fun logGestureDetectionEndedWithoutTriggering(tag: String, y: Int) {
buffer.log(
- TAG,
+ tag,
LogLevel.DEBUG,
{ int1 = y },
{ "Gesture finished; no swipe up gesture detected. Final y=$int1" }
)
}
- fun logGestureDetected(y: Int) {
+ fun logGestureDetected(tag: String, y: Int) {
buffer.log(
- TAG,
+ tag,
LogLevel.INFO,
{ int1 = y },
{ "Gesture detected; notifying callbacks. y=$int1" }
)
}
- fun logInputListeningStarted() {
- buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening started "})
+ fun logInputListeningStarted(tag: String) {
+ buffer.log(tag, LogLevel.VERBOSE, {}, { "Input listening started "})
}
- fun logInputListeningStopped() {
- buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening stopped "})
+ fun logInputListeningStopped(tag: String) {
+ buffer.log(tag, LogLevel.VERBOSE, {}, { "Input listening stopped "})
}
}
-
-private const val TAG = "SwipeStatusBarAwayGestureHandler"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 05a9a42..635ed7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -25,6 +25,10 @@
val context: Context,
val featureFlags: FeatureFlags
) {
+ init {
+ featureFlags.addListener(Flags.DISABLE_FSI) { event -> event.requestNoRestart() }
+ }
+
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
@@ -35,6 +39,8 @@
fun fsiOnDNDUpdate(): Boolean = featureFlags.isEnabled(Flags.FSI_ON_DND_UPDATE)
+ fun disableFsi(): Boolean = featureFlags.isEnabled(Flags.DISABLE_FSI)
+
val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 7136cad..bc881ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -31,6 +31,10 @@
*/
enum FullScreenIntentDecision {
/**
+ * Full screen intents are disabled.
+ */
+ NO_FSI_DISABLED(false),
+ /**
* No full screen intent included, so there is nothing to show.
*/
NO_FULL_SCREEN_INTENT(false),
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 d9dacfd..9bcf92d 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
@@ -28,7 +28,6 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.service.notification.StatusBarNotification;
@@ -40,6 +39,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -74,6 +74,7 @@
private final NotifPipelineFlags mFlags;
private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
private final UiEventLogger mUiEventLogger;
+ private final UserTracker mUserTracker;
@VisibleForTesting
protected boolean mUseHeadsUp = false;
@@ -114,7 +115,8 @@
@Main Handler mainHandler,
NotifPipelineFlags flags,
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker) {
mContentResolver = contentResolver;
mPowerManager = powerManager;
mDreamManager = dreamManager;
@@ -127,6 +129,7 @@
mFlags = flags;
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
mUiEventLogger = uiEventLogger;
+ mUserTracker = userTracker;
ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -236,6 +239,9 @@
@Override
public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
+ if (mFlags.disableFsi()) {
+ return FullScreenIntentDecision.NO_FSI_DISABLED;
+ }
if (entry.getSbn().getNotification().fullScreenIntent == null) {
return FullScreenIntentDecision.NO_FULL_SCREEN_INTENT;
}
@@ -325,6 +331,9 @@
final int uid = entry.getSbn().getUid();
final String packageName = entry.getSbn().getPackageName();
switch (decision) {
+ case NO_FSI_DISABLED:
+ mLogger.logNoFullscreen(entry, "Disabled");
+ return;
case NO_FULL_SCREEN_INTENT:
return;
case NO_FSI_SUPPRESSED_BY_DND:
@@ -450,7 +459,7 @@
* @return true if the entry should ambient pulse, false otherwise
*/
private boolean shouldHeadsUpWhenDozing(NotificationEntry entry, boolean log) {
- if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
+ if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(mUserTracker.getUserId())) {
if (log) mLogger.logNoPulsingSettingDisabled(entry);
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
index ffd931c..197ae1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
@@ -18,9 +18,12 @@
package com.android.systemui.statusbar.notification.logging
import android.stats.sysui.NotificationEnums
+import android.util.Log
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.dump.Row
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import java.io.PrintWriter
import javax.inject.Inject
@@ -33,6 +36,7 @@
fun init() {
dumpManager.registerNormalDumpable(javaClass.simpleName, this)
+ Log.i("NotificationMemory", "Registered dumpable.")
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -45,27 +49,36 @@
/** Renders a table of notification object usage into passed [PrintWriter]. */
private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
- pw.println("Notification Object Usage")
- pw.println("-----------")
- pw.println(
- "Package".padEnd(35) +
- "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
- )
- pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
- pw.println()
-
- memoryUse.forEach { use ->
- pw.println(
- use.packageName.padEnd(35) +
- "\t\t" +
- "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
- (styleEnumToString(use.objectUsage.style).take(15) ?: "").padEnd(15) +
- "\t\t${use.objectUsage.styleIcon}\t" +
- "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
- "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
- use.notificationKey
+ val columns =
+ listOf(
+ "Package",
+ "Small Icon",
+ "Large Icon",
+ "Style",
+ "Style Icon",
+ "Big Picture",
+ "Extender",
+ "Extras",
+ "Custom View",
+ "Key"
)
- }
+ val rows: List<Row> =
+ memoryUse.map {
+ listOf(
+ it.packageName,
+ toKb(it.objectUsage.smallIcon),
+ toKb(it.objectUsage.largeIcon),
+ styleEnumToString(it.objectUsage.style),
+ toKb(it.objectUsage.styleIcon),
+ toKb(it.objectUsage.bigPicture),
+ toKb(it.objectUsage.extender),
+ toKb(it.objectUsage.extras),
+ it.objectUsage.hasCustomView.toString(),
+ // | is a field delimiter in the output format so we need to replace
+ // it to avoid breakage.
+ it.notificationKey.replace('|', '│')
+ )
+ }
// Calculate totals for easily glanceable summary.
data class Totals(
@@ -88,18 +101,23 @@
t
}
- pw.println()
- pw.println("TOTALS")
- pw.println(
- "".padEnd(35) +
- "\t\t" +
- "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
- "".padEnd(15) +
- "\t\t${toKb(totals.styleIcon)}\t" +
- "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
- toKb(totals.extras)
- )
- pw.println()
+ val totalsRow: List<Row> =
+ listOf(
+ listOf(
+ "TOTALS",
+ toKb(totals.smallIcon),
+ toKb(totals.largeIcon),
+ "",
+ toKb(totals.styleIcon),
+ toKb(totals.bigPicture),
+ toKb(totals.extender),
+ toKb(totals.extras),
+ "",
+ ""
+ )
+ )
+ val tableLogger = DumpsysTableLogger("Notification Object Usage", columns, rows + totalsRow)
+ tableLogger.printTableData(pw)
}
/** Renders a table of notification view usage into passed [PrintWriter] */
@@ -116,40 +134,65 @@
var softwareBitmapsPenalty: Int = 0,
)
- val totals = Totals()
- pw.println("Notification View Usage")
- pw.println("-----------")
- pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
- pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
- pw.println()
- memoryUse
- .filter { it.viewUsage.isNotEmpty() }
- .forEach { use ->
- pw.println(use.packageName + " " + use.notificationKey)
- use.viewUsage.forEach { view ->
- pw.println(
- " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
- "\t${view.largeIcon}\t${view.style}" +
- "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
- )
-
- if (view.viewType == ViewType.TOTAL) {
- totals.smallIcon += view.smallIcon
- totals.largeIcon += view.largeIcon
- totals.style += view.style
- totals.customViews += view.customViews
- totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
+ val columns =
+ listOf(
+ "Package",
+ "View Type",
+ "Small Icon",
+ "Large Icon",
+ "Style Use",
+ "Custom View",
+ "Software Bitmaps",
+ "Key"
+ )
+ val rows =
+ memoryUse
+ .filter { it.viewUsage.isNotEmpty() }
+ .flatMap { use ->
+ use.viewUsage.map { view ->
+ listOf(
+ use.packageName,
+ view.viewType.toString(),
+ toKb(view.smallIcon),
+ toKb(view.largeIcon),
+ toKb(view.style),
+ toKb(view.customViews),
+ toKb(view.softwareBitmapsPenalty),
+ // | is a field delimiter in the output format so we need to replace
+ // it to avoid breakage.
+ use.notificationKey.replace('|', '│')
+ )
}
}
+
+ val totals = Totals()
+ memoryUse
+ .filter { it.viewUsage.isNotEmpty() }
+ .map { it.viewUsage.firstOrNull { view -> view.viewType == ViewType.TOTAL } }
+ .filterNotNull()
+ .forEach { view ->
+ totals.smallIcon += view.smallIcon
+ totals.largeIcon += view.largeIcon
+ totals.style += view.style
+ totals.customViews += view.customViews
+ totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
}
- pw.println()
- pw.println("TOTALS")
- pw.println(
- " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
- "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
- "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
- )
- pw.println()
+
+ val totalsRow: List<Row> =
+ listOf(
+ listOf(
+ "TOTALS",
+ "",
+ toKb(totals.smallIcon),
+ toKb(totals.largeIcon),
+ toKb(totals.style),
+ toKb(totals.customViews),
+ toKb(totals.softwareBitmapsPenalty),
+ ""
+ )
+ )
+ val tableLogger = DumpsysTableLogger("Notification View Usage", columns, rows + totalsRow)
+ tableLogger.printTableData(pw)
}
private fun styleEnumToString(styleEnum: Int): String =
@@ -168,6 +211,10 @@
}
private fun toKb(bytes: Int): String {
- return (bytes / 1024).toString() + " KB"
+ if (bytes == 0) {
+ return "--"
+ }
+
+ return "%.2f KB".format(bytes / 1024f)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index fbe88df..7addc8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -24,6 +24,7 @@
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.MathUtils;
+import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -492,12 +493,9 @@
if (animationListener != null) {
mAppearAnimator.addListener(animationListener);
}
- if (delay > 0) {
- // we need to apply the initial state already to avoid drawn frames in the wrong state
- updateAppearAnimationAlpha();
- updateAppearRect();
- mAppearAnimator.setStartDelay(delay);
- }
+ // we need to apply the initial state already to avoid drawn frames in the wrong state
+ updateAppearAnimationAlpha();
+ updateAppearRect();
mAppearAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mWasCancelled;
@@ -528,7 +526,20 @@
mWasCancelled = true;
}
});
- mAppearAnimator.start();
+
+ // Cache the original animator so we can check if the animation should be started in the
+ // Choreographer callback. It's possible that the original animator (mAppearAnimator) is
+ // replaced with a new value before the callback is called.
+ ValueAnimator cachedAnimator = mAppearAnimator;
+ // Even when delay=0, starting the animation on the next frame is necessary to avoid jank.
+ // Not doing so will increase the chances our Animator will be forced to skip a value of
+ // the animation's progression, causing stutter.
+ Choreographer.getInstance().postFrameCallbackDelayed(
+ frameTimeNanos -> {
+ if (mAppearAnimator == cachedAnimator) {
+ mAppearAnimator.start();
+ }
+ }, delay);
}
private int getCujType(boolean isAppearing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 49dc655..21f4cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -16,15 +16,22 @@
package com.android.systemui.statusbar.notification.row;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
import android.view.View;
+import android.widget.TextView;
import androidx.annotation.NonNull;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -41,6 +48,11 @@
private String mManageNotificationText;
private String mManageNotificationHistoryText;
+ // Footer label
+ private TextView mSeenNotifsFooterTextView;
+ private @StringRes int mSeenNotifsFilteredText;
+ private int mUnlockIconSize;
+
public FooterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -73,10 +85,41 @@
super.onFinishInflate();
mClearAllButton = (FooterViewButton) findSecondaryView();
mManageButton = findViewById(R.id.manage_text);
+ mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
updateResources();
updateText();
}
+ public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) {
+ mSeenNotifsFilteredText = text;
+ if (mSeenNotifsFilteredText != 0) {
+ mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+ } else {
+ mSeenNotifsFooterTextView.setText(null);
+ }
+ Drawable drawable;
+ if (icon == 0) {
+ drawable = null;
+ } else {
+ drawable = getResources().getDrawable(icon);
+ drawable.setBounds(0, 0, mUnlockIconSize, mUnlockIconSize);
+ }
+ mSeenNotifsFooterTextView.setCompoundDrawablesRelative(drawable, null, null, null);
+ updateFooterVisibilityMode();
+ }
+
+ private void updateFooterVisibilityMode() {
+ if (mSeenNotifsFilteredText != 0) {
+ mManageButton.setVisibility(View.GONE);
+ mClearAllButton.setVisibility(View.GONE);
+ mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+ } else {
+ mManageButton.setVisibility(View.VISIBLE);
+ mClearAllButton.setVisibility(View.VISIBLE);
+ mSeenNotifsFooterTextView.setVisibility(View.GONE);
+ }
+ }
+
public void setManageButtonClickListener(OnClickListener listener) {
mManageButton.setOnClickListener(listener);
}
@@ -135,12 +178,19 @@
mClearAllButton.setTextColor(textColor);
mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
mManageButton.setTextColor(textColor);
+ final @ColorInt int labelTextColor =
+ Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
+ mSeenNotifsFooterTextView.setTextColor(labelTextColor);
+ mSeenNotifsFooterTextView.setCompoundDrawableTintList(
+ ColorStateList.valueOf(labelTextColor));
}
private void updateResources() {
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
+ mUnlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
}
@Override
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 4837075..aab36da 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
@@ -538,6 +538,7 @@
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
+ private boolean mHasFilteredOutSeenNotifications;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
new ExpandableView.OnHeightChangedListener() {
@@ -684,6 +685,10 @@
updateFooter();
}
+ void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
+ mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
+ }
+
@VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
@@ -4612,13 +4617,12 @@
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- void updateEmptyShadeView(
- boolean visible, boolean areNotificationsHiddenInShade, boolean areSeenNotifsFiltered) {
+ void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
if (areNotificationsHiddenInShade) {
updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
- } else if (areSeenNotifsFiltered) {
+ } else if (mHasFilteredOutSeenNotifications) {
updateEmptyShadeView(
R.string.no_unseen_notif_text,
R.string.unlock_to_see_notif_text,
@@ -4657,13 +4661,20 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
- if (mFooterView == null) {
+ if (mFooterView == null || mNotificationStackSizeCalculator == null) {
return;
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
mFooterView.setSecondaryVisible(showDismissView, animate);
mFooterView.showHistory(showHistory);
+ if (mHasFilteredOutSeenNotifications) {
+ mFooterView.setFooterLabelTextAndIcon(
+ R.string.unlock_to_see_notif_text,
+ R.drawable.ic_friction_lock_closed);
+ } else {
+ mFooterView.setFooterLabelTextAndIcon(0, 0);
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5891948..42d122d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -120,6 +120,7 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Compile;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
import java.util.List;
@@ -189,6 +190,7 @@
private final FeatureFlags mFeatureFlags;
private final boolean mUseRoundnessSourceTypes;
private final NotificationTargetsHelper mNotificationTargetsHelper;
+ private final SecureSettings mSecureSettings;
private View mLongPressedView;
@@ -667,7 +669,8 @@
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
FeatureFlags featureFlags,
- NotificationTargetsHelper notificationTargetsHelper) {
+ NotificationTargetsHelper notificationTargetsHelper,
+ SecureSettings secureSettings) {
mStackStateLogger = stackLogger;
mLogger = logger;
mAllowLongPress = allowLongPress;
@@ -709,6 +712,7 @@
mFeatureFlags = featureFlags;
mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mNotificationTargetsHelper = notificationTargetsHelper;
+ mSecureSettings = secureSettings;
updateResources();
}
@@ -1015,8 +1019,7 @@
Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
return false;
}
- mHistoryEnabled = historyEnabled = Settings.Secure.getIntForUser(
- mView.getContext().getContentResolver(),
+ mHistoryEnabled = historyEnabled = mSecureSettings.getIntForUser(
Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
0,
UserHandle.USER_CURRENT) == 1;
@@ -1242,11 +1245,7 @@
// For more details, see: b/228790482
&& !isInTransitionToKeyguard();
- mView.updateEmptyShadeView(
- shouldShow,
- mZenModeController.areNotificationsHiddenInShade(),
- mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
- && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
+ mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
Trace.endSection();
}
@@ -1942,6 +1941,9 @@
@Override
public void setNotifStats(@NonNull NotifStats notifStats) {
mNotifStats = notifStats;
+ mView.setHasFilteredOutSeenNotifications(
+ mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
+ && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
updateFooter();
updateShowEmptyShadeView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 936589c..856d7de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -31,7 +31,6 @@
import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -55,6 +54,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
@@ -99,6 +99,7 @@
private final Optional<Vibrator> mVibratorOptional;
private final DisableFlagsLogger mDisableFlagsLogger;
private final int mDisplayId;
+ private final UserTracker mUserTracker;
private final boolean mVibrateOnOpening;
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
private final SystemBarAttributesListener mSystemBarAttributesListener;
@@ -133,7 +134,8 @@
DisableFlagsLogger disableFlagsLogger,
@DisplayId int displayId,
SystemBarAttributesListener systemBarAttributesListener,
- Lazy<CameraLauncher> cameraLauncherLazy) {
+ Lazy<CameraLauncher> cameraLauncherLazy,
+ UserTracker userTracker) {
mCentralSurfaces = centralSurfaces;
mContext = context;
mShadeController = shadeController;
@@ -157,6 +159,7 @@
mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
mCameraLauncherLazy = cameraLauncherLazy;
+ mUserTracker = userTracker;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -215,7 +218,7 @@
return;
}
- mNotificationPanelViewController.expandWithoutQs();
+ mNotificationPanelViewController.expandShadeToNotifications();
}
@Override
@@ -375,7 +378,7 @@
mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
false /* onlyProvisioned */, true /* dismissShade */,
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
- null /* animationController */, UserHandle.CURRENT);
+ null /* animationController */, mUserTracker.getUserHandle());
} else {
if (!mCentralSurfaces.isDeviceInteractive()) {
// Avoid flickering of the scrim when we instant launch the camera and the bouncer
@@ -432,7 +435,7 @@
mCentralSurfaces.startActivityDismissingKeyguard(emergencyIntent,
false /* onlyProvisioned */, true /* dismissShade */,
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
- null /* animationController */, UserHandle.CURRENT);
+ null /* animationController */, mUserTracker.getUserHandle());
return;
}
@@ -447,7 +450,7 @@
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
- mContext.startActivityAsUser(emergencyIntent, UserHandle.CURRENT);
+ mContext.startActivityAsUser(emergencyIntent, mUserTracker.getUserHandle());
return;
}
// We need to defer the emergency action launch until the screen comes on, since otherwise
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 ec08bd4..194d1a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -178,6 +178,7 @@
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.scrim.ScrimView;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -507,6 +508,7 @@
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final MessageRouter mMessageRouter;
private final WallpaperManager mWallpaperManager;
+ private final UserTracker mUserTracker;
private CentralSurfacesComponent mCentralSurfacesComponent;
@@ -750,7 +752,8 @@
IDreamManager dreamManager,
Lazy<CameraLauncher> cameraLauncherLazy,
Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy,
- AlternateBouncerInteractor alternateBouncerInteractor
+ AlternateBouncerInteractor alternateBouncerInteractor,
+ UserTracker userTracker
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -830,6 +833,7 @@
mJankMonitor = jankMonitor;
mCameraLauncherLazy = cameraLauncherLazy;
mAlternateBouncerInteractor = alternateBouncerInteractor;
+ mUserTracker = userTracker;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -867,6 +871,11 @@
wiredChargingRippleController.registerCallbacks();
mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
+
+ // Based on teamfood flag, turn predictive back dispatch on at runtime.
+ if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
+ mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
+ }
}
@Override
@@ -981,11 +990,6 @@
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy.init();
- // Based on teamfood flag, turn predictive back dispatch on at runtime.
- if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
- mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
- }
-
mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
@@ -1397,6 +1401,7 @@
// Things that mean we're not swiping to dismiss the keyguard, and should ignore this
// expansion:
// - Keyguard isn't even visible.
+ // - We're swiping on the bouncer, not the lockscreen.
// - Keyguard is occluded. Expansion changes here are the shade being expanded over the
// occluding activity.
// - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt).
@@ -1406,6 +1411,7 @@
// - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
// keyguard.
if (!isKeyguardShowing()
+ || mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
|| isOccluded()
|| !mKeyguardStateController.canDismissLockScreen()
|| mKeyguardViewMediator.isAnySimPinSecure()
@@ -4153,7 +4159,8 @@
Log.wtf(TAG, "WallpaperManager not supported");
return;
}
- WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(UserHandle.USER_CURRENT);
+ WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(
+ mUserTracker.getUserId());
mWallpaperController.onWallpaperInfoUpdated(info);
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
@@ -4377,6 +4384,6 @@
return new UserHandle(UserHandle.myUserId());
}
}
- return UserHandle.CURRENT;
+ return mUserTracker.getUserHandle();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 0446cef..c248a50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -45,6 +45,7 @@
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -84,6 +85,7 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final FoldAodAnimationController mFoldAodAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ private final UserTracker mUserTracker;
private final Set<Callback> mCallbacks = new HashSet<>();
@@ -128,7 +130,8 @@
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
ConfigurationController configurationController,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ UserTracker userTracker) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -140,6 +143,7 @@
mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
mScreenOffAnimationController = screenOffAnimationController;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ mUserTracker = userTracker;
keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
tunerService.addTunable(
@@ -169,7 +173,7 @@
private void updateQuickPickupEnabled() {
mIsQuickPickupEnabled =
- mAmbientDisplayConfiguration.quickPickupSensorEnabled(UserHandle.USER_CURRENT);
+ mAmbientDisplayConfiguration.quickPickupSensorEnabled(mUserTracker.getUserId());
}
public boolean getDisplayStateSupported() {
@@ -418,7 +422,7 @@
@Override
public void onTuningChanged(String key, String newValue) {
- mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+ mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(mUserTracker.getUserId());
if (key.equals(Settings.Secure.DOZE_ALWAYS_ON)) {
updateControlScreenOff();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d318759..6873de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -26,8 +26,6 @@
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@@ -177,11 +175,6 @@
*/
public void show(boolean resetSecuritySelection, boolean isScrimmed) {
final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser();
- if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
- // In split system user mode, we never unlock system user.
- return;
- }
-
try {
Trace.beginSection("KeyguardBouncer#show");
@@ -212,9 +205,7 @@
}
final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
- final boolean isSystemUser =
- UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
- final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
+ final boolean allowDismissKeyguard = activeUserId == keyguardUserId;
// If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern)
// is set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
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 4ad3199..cba0897 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -357,7 +357,7 @@
mView.setOnApplyWindowInsetsListener(
(view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
mSecureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON),
+ Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
false,
mVolumeSettingObserver,
UserHandle.USER_ALL);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 48e58fc..6c532a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -35,7 +35,6 @@
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
@@ -58,6 +57,7 @@
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
@@ -136,6 +136,7 @@
private final UserInfoController mUserInfoController;
private final IActivityManager mIActivityManager;
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final DevicePolicyManager mDevicePolicyManager;
private final StatusBarIconController mIconController;
private final CommandQueue mCommandQueue;
@@ -176,7 +177,7 @@
KeyguardStateController keyguardStateController,
LocationController locationController,
SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager,
- AlarmManager alarmManager, UserManager userManager,
+ AlarmManager alarmManager, UserManager userManager, UserTracker userTracker,
DevicePolicyManager devicePolicyManager, RecordingController recordingController,
@Nullable TelecomManager telecomManager, @DisplayId int displayId,
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
@@ -196,6 +197,7 @@
mUserInfoController = userInfoController;
mIActivityManager = iActivityManager;
mUserManager = userManager;
+ mUserTracker = userTracker;
mDevicePolicyManager = devicePolicyManager;
mRotationLockController = rotationLockController;
mDataSaver = dataSaverController;
@@ -366,7 +368,7 @@
}
private void updateAlarm() {
- final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+ final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
int zen = mZenController.getZen();
final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f784723..80093a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -791,20 +791,28 @@
if (!mScreenOffAnimationController.shouldExpandNotifications()
&& !mAnimatingPanelExpansionOnUnlock
&& !occluding) {
- float behindFraction = getInterpolatedFraction();
- behindFraction = (float) Math.pow(behindFraction, 0.8f);
if (mClipsQsScrim) {
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
mNotificationsAlpha =
mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
} else {
- mBehindAlpha =
- mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
- // Delay fade-in of notification scrim a bit further, to coincide with the
- // view fade in. Otherwise the empty panel can be quite jarring.
- mNotificationsAlpha = mTransparentScrimBackground
- ? 0 : MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
- mPanelExpansionFraction);
+ if (mTransparentScrimBackground) {
+ mBehindAlpha = 0;
+ mNotificationsAlpha = 0;
+ } else {
+ // Behind scrim will finish fading in at 30% expansion.
+ float behindFraction = MathUtils
+ .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction);
+ mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+ // Delay fade-in of notification scrim a bit further, to coincide with the
+ // behind scrim finishing fading in.
+ // Also to coincide with the view starting to fade in, otherwise the empty
+ // panel can be quite jarring.
+ mNotificationsAlpha = MathUtils
+ .constrainedMap(0f, 1f, 0.3f, 0.75f, mPanelExpansionFraction);
+ }
}
mBehindTint = mState.getBehindTint();
mInFrontAlpha = 0;
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 001da6f..f7b8745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1146,7 +1146,7 @@
if (hideImmediately) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
} else {
- mNotificationPanelViewController.expandWithoutQs();
+ mNotificationPanelViewController.expandShadeToNotifications();
}
}
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index be6e0cc..078a00d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -53,6 +53,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -118,6 +119,7 @@
private final NotificationPanelViewController mNotificationPanel;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
+ private final UserTracker mUserTracker;
private final OnUserInteractionCallback mOnUserInteractionCallback;
private boolean mIsCollapsingToShowActivityOverLockscreen;
@@ -153,7 +155,8 @@
ActivityLaunchAnimator activityLaunchAnimator,
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ UserTracker userTracker) {
mContext = context;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
@@ -184,6 +187,7 @@
mNotificationPanel = panel;
mActivityLaunchAnimator = activityLaunchAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
+ mUserTracker = userTracker;
launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
}
@@ -518,7 +522,7 @@
intent.getPackage(),
(adapter) -> tsb.startActivities(
getActivityOptions(mCentralSurfaces.getDisplayId(), adapter),
- UserHandle.CURRENT));
+ mUserTracker.getUserHandle()));
});
return true;
}
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 f09c79b..cbd27cf 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
@@ -304,7 +304,7 @@
mAnimationScheduler.addCallback(this);
mSecureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON),
+ Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
false,
mVolumeSettingObserver,
UserHandle.USER_ALL);
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index ad48e21..df8d161 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -31,6 +31,7 @@
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
import androidx.annotation.CallSuper
+import androidx.annotation.VisibleForTesting
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Main
@@ -108,9 +109,10 @@
* Whenever the current view disappears, the next-priority view will be displayed if it's still
* valid.
*/
+ @VisibleForTesting
internal val activeViews: MutableList<DisplayInfo> = mutableListOf()
- private fun getCurrentDisplayInfo(): DisplayInfo? {
+ internal fun getCurrentDisplayInfo(): DisplayInfo? {
return activeViews.getOrNull(0)
}
@@ -119,15 +121,26 @@
dumpManager.registerNormalDumpable(this)
}
+ private val listeners: MutableSet<Listener> = mutableSetOf()
+
+ /** Registers a listener. */
+ fun registerListener(listener: Listener) {
+ listeners.add(listener)
+ }
+
+ /** Unregisters a listener. */
+ fun unregisterListener(listener: Listener) {
+ listeners.remove(listener)
+ }
+
/**
* Displays the view with the provided [newInfo].
*
* This method handles inflating and attaching the view, then delegates to [updateView] to
* display the correct information in the view.
- * @param onViewTimeout a runnable that runs after the view timeout.
*/
@Synchronized
- fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
+ fun displayView(newInfo: T) {
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
newInfo.timeoutMs,
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
@@ -146,14 +159,13 @@
logger.logViewUpdate(newInfo)
currentDisplayInfo.info = newInfo
currentDisplayInfo.timeExpirationMillis = timeExpirationMillis
- updateTimeout(currentDisplayInfo, timeout, onViewTimeout)
+ updateTimeout(currentDisplayInfo, timeout)
updateView(newInfo, view)
return
}
val newDisplayInfo = DisplayInfo(
info = newInfo,
- onViewTimeout = onViewTimeout,
timeExpirationMillis = timeExpirationMillis,
// Null values will be updated to non-null if/when this view actually gets displayed
view = null,
@@ -196,7 +208,7 @@
private fun showNewView(newDisplayInfo: DisplayInfo, timeout: Int) {
logger.logViewAddition(newDisplayInfo.info)
createAndAcquireWakeLock(newDisplayInfo)
- updateTimeout(newDisplayInfo, timeout, newDisplayInfo.onViewTimeout)
+ updateTimeout(newDisplayInfo, timeout)
inflateAndUpdateView(newDisplayInfo)
}
@@ -227,19 +239,16 @@
/**
* Creates a runnable that will remove [displayInfo] in [timeout] ms from now.
*
- * @param onViewTimeout an optional runnable that will be run if the view times out.
* @return a runnable that, when run, will *cancel* the view's timeout.
*/
- private fun updateTimeout(displayInfo: DisplayInfo, timeout: Int, onViewTimeout: Runnable?) {
+ private fun updateTimeout(displayInfo: DisplayInfo, timeout: Int) {
val cancelViewTimeout = mainExecutor.executeDelayed(
{
removeView(displayInfo.info.id, REMOVAL_REASON_TIMEOUT)
- onViewTimeout?.run()
},
timeout.toLong()
)
- displayInfo.onViewTimeout = onViewTimeout
// Cancel old view timeout and re-set it.
displayInfo.cancelViewTimeout?.run()
displayInfo.cancelViewTimeout = cancelViewTimeout
@@ -317,6 +326,9 @@
// event comes in while this view is animating out, we still display the new view
// appropriately.
activeViews.remove(displayInfo)
+ listeners.forEach {
+ it.onInfoPermanentlyRemoved(id)
+ }
// No need to time the view out since it's already gone
displayInfo.cancelViewTimeout?.run()
@@ -380,6 +392,9 @@
invalidViews.forEach {
activeViews.remove(it)
logger.logViewExpiration(it.info)
+ listeners.forEach { listener ->
+ listener.onInfoPermanentlyRemoved(it.info.id)
+ }
}
}
@@ -436,6 +451,15 @@
onAnimationEnd.run()
}
+ /** A listener interface to be notified of various view events. */
+ fun interface Listener {
+ /**
+ * Called whenever a [DisplayInfo] with the given [id] has been removed and will never be
+ * displayed again (unless another call to [updateView] is made).
+ */
+ fun onInfoPermanentlyRemoved(id: String)
+ }
+
/** A container for all the display-related state objects. */
inner class DisplayInfo(
/**
@@ -461,11 +485,6 @@
var wakeLock: WakeLock?,
/**
- * See [displayView].
- */
- var onViewTimeout: Runnable?,
-
- /**
* A runnable that, when run, will cancel this view's timeout.
*
* Null if this info isn't currently being displayed.
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 04b1a50..9e0bbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -80,6 +80,7 @@
powerManager: PowerManager,
private val falsingManager: FalsingManager,
private val falsingCollector: FalsingCollector,
+ private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
private val viewUtil: ViewUtil,
private val vibratorHelper: VibratorHelper,
wakeLockBuilder: WakeLock.Builder,
@@ -105,6 +106,8 @@
commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) }
override fun updateView(newInfo: ChipbarInfo, currentView: ViewGroup) {
+ updateGestureListening()
+
logger.logViewUpdate(
newInfo.windowTitle,
newInfo.text.loadText(context),
@@ -228,6 +231,42 @@
includeMargins = true,
onAnimationEnd,
)
+
+ updateGestureListening()
+ }
+
+ private fun updateGestureListening() {
+ if (swipeChipbarAwayGestureHandler == null) {
+ return
+ }
+
+ val currentDisplayInfo = getCurrentDisplayInfo()
+ if (currentDisplayInfo != null && currentDisplayInfo.info.allowSwipeToDismiss) {
+ swipeChipbarAwayGestureHandler.setViewFetcher { currentDisplayInfo.view }
+ swipeChipbarAwayGestureHandler.addOnGestureDetectedCallback(TAG) {
+ onSwipeUpGestureDetected()
+ }
+ } else {
+ swipeChipbarAwayGestureHandler.resetViewFetcher()
+ swipeChipbarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
+ }
+ }
+
+ private fun onSwipeUpGestureDetected() {
+ val currentDisplayInfo = getCurrentDisplayInfo()
+ if (currentDisplayInfo == null) {
+ logger.logSwipeGestureError(id = null, errorMsg = "No info is being displayed")
+ return
+ }
+ if (!currentDisplayInfo.info.allowSwipeToDismiss) {
+ logger.logSwipeGestureError(
+ id = currentDisplayInfo.info.id,
+ errorMsg = "This view prohibits swipe-to-dismiss",
+ )
+ return
+ }
+ removeView(currentDisplayInfo.info.id, SWIPE_UP_GESTURE_REASON)
+ updateGestureListening()
}
private fun ViewGroup.getInnerView(): ViewGroup {
@@ -250,3 +289,5 @@
private const val ANIMATION_IN_DURATION = 500L
private const val ANIMATION_OUT_DURATION = 250L
@IdRes private val INFO_TAG = R.id.tag_chipbar_info
+private const val SWIPE_UP_GESTURE_REASON = "SWIPE_UP_GESTURE_DETECTED"
+private const val TAG = "ChipbarCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index dd4bd26..fe46318 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -33,12 +33,14 @@
* @property endItem an optional end item to display at the end of the chipbar (on the right in LTR
* locales; on the left in RTL locales).
* @property vibrationEffect an optional vibration effect when the chipbar is displayed
+ * @property allowSwipeToDismiss true if users are allowed to swipe up to dismiss this chipbar.
*/
data class ChipbarInfo(
val startIcon: TintedIcon,
val text: Text,
val endItem: ChipbarEndItem?,
val vibrationEffect: VibrationEffect? = null,
+ val allowSwipeToDismiss: Boolean = false,
override val windowTitle: String,
override val wakeReason: String,
override val timeoutMs: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index fcfbe0a..f239428 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -46,4 +46,16 @@
{ "Chipbar updated. window=$str1 text=$str2 endItem=$str3" }
)
}
+
+ fun logSwipeGestureError(id: String?, errorMsg: String) {
+ buffer.log(
+ tag,
+ LogLevel.WARNING,
+ {
+ str1 = id
+ str2 = errorMsg
+ },
+ { "Chipbar swipe gesture detected for incorrect state. id=$str1 error=$str2" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
new file mode 100644
index 0000000..6e3cb48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.systemui.temporarydisplay.chipbar
+
+import android.content.Context
+import android.view.MotionEvent
+import android.view.View
+import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler
+import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
+import com.android.systemui.util.boundsOnScreen
+
+/**
+ * A class to detect when a user has swiped the chipbar away.
+ *
+ * Effectively [SysUISingleton]. But, this shouldn't be created if the gesture isn't enabled. See
+ * [TemporaryDisplayModule.provideSwipeChipbarAwayGestureHandler].
+ */
+class SwipeChipbarAwayGestureHandler(
+ context: Context,
+ logger: SwipeUpGestureLogger,
+) : SwipeUpGestureHandler(context, logger, loggerTag = LOGGER_TAG) {
+
+ private var viewFetcher: () -> View? = { null }
+
+ override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean {
+ val view = viewFetcher.invoke() ?: return false
+ // Since chipbar is in its own window, we need to use [boundsOnScreen] to get an accurate
+ // bottom. ([view.bottom] would be relative to its window, which would be too small.)
+ val viewBottom = view.boundsOnScreen.bottom
+ // Allow the gesture to start a bit below the chipbar
+ return ev.y <= 1.5 * viewBottom
+ }
+
+ /**
+ * Sets a fetcher that returns the current chipbar view. The fetcher will be invoked whenever a
+ * gesture starts to determine if the gesture is near the chipbar.
+ */
+ fun setViewFetcher(fetcher: () -> View?) {
+ viewFetcher = fetcher
+ }
+
+ /** Removes the current view fetcher. */
+ fun resetViewFetcher() {
+ viewFetcher = { null }
+ }
+}
+
+private const val LOGGER_TAG = "SwipeChipbarAway"
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
index cf0a183..933c060 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -16,22 +16,38 @@
package com.android.systemui.temporarydisplay.dagger
+import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
+import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
import dagger.Module
import dagger.Provides
@Module
interface TemporaryDisplayModule {
- @Module
companion object {
- @JvmStatic
@Provides
@SysUISingleton
@ChipbarLog
fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("ChipbarLog", 40)
}
+
+ @Provides
+ @SysUISingleton
+ fun provideSwipeChipbarAwayGestureHandler(
+ mediaTttFlags: MediaTttFlags,
+ context: Context,
+ logger: SwipeUpGestureLogger,
+ ): SwipeChipbarAwayGestureHandler? {
+ return if (mediaTttFlags.isMediaTttDismissGestureEnabled()) {
+ SwipeChipbarAwayGestureHandler(context, logger)
+ } else {
+ null
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 5894fd3..9f6e602 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -26,7 +26,6 @@
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
-import android.annotation.Nullable;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.app.WallpaperManager.OnColorsChangedListener;
@@ -70,6 +69,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.monet.Style;
+import com.android.systemui.monet.TonalPalette;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -389,7 +389,7 @@
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
UserHandle.ALL);
mSecureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
false,
new ContentObserver(mBgHandler) {
@Override
@@ -511,39 +511,42 @@
/**
* Given a color candidate, return an overlay definition.
*/
- protected @Nullable FabricatedOverlay getOverlay(int color, int type, Style style) {
+ protected FabricatedOverlay getOverlay(int color, int type, Style style) {
boolean nightMode = (mResources.getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
mColorScheme = new ColorScheme(color, nightMode, style);
- List<Integer> colorShades = type == ACCENT
- ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors();
String name = type == ACCENT ? "accent" : "neutral";
- int paletteSize = mColorScheme.getAccent1().size();
+
FabricatedOverlay.Builder overlay =
new FabricatedOverlay.Builder("com.android.systemui", name, "android");
- for (int i = 0; i < colorShades.size(); i++) {
- int luminosity = i % paletteSize;
- int paletteIndex = i / paletteSize + 1;
- String resourceName;
- switch (luminosity) {
- case 0:
- resourceName = "android:color/system_" + name + paletteIndex + "_10";
- break;
- case 1:
- resourceName = "android:color/system_" + name + paletteIndex + "_50";
- break;
- default:
- int l = luminosity - 1;
- resourceName = "android:color/system_" + name + paletteIndex + "_" + l + "00";
- }
- overlay.setResourceValue(resourceName, TypedValue.TYPE_INT_COLOR_ARGB8,
- ColorUtils.setAlphaComponent(colorShades.get(i), 0xFF));
+
+ if (type == ACCENT) {
+ assignTonalPaletteToOverlay("accent1", overlay, mColorScheme.getAccent1());
+ assignTonalPaletteToOverlay("accent2", overlay, mColorScheme.getAccent2());
+ assignTonalPaletteToOverlay("accent3", overlay, mColorScheme.getAccent3());
+ } else {
+ assignTonalPaletteToOverlay("neutral1", overlay, mColorScheme.getNeutral1());
+ assignTonalPaletteToOverlay("neutral2", overlay, mColorScheme.getNeutral2());
}
return overlay.build();
}
+ private void assignTonalPaletteToOverlay(String name,
+ FabricatedOverlay.Builder overlay, TonalPalette tonalPalette) {
+
+ String resourcePrefix = "android:color/system_" + name;
+ int colorDataType = TypedValue.TYPE_INT_COLOR_ARGB8;
+
+ tonalPalette.getAllShadesMapped().forEach((key, value) -> {
+ String resourceName = resourcePrefix + "_" + key;
+ int colorValue = ColorUtils.setAlphaComponent(value, 0xFF);
+ overlay.setResourceValue(resourceName, colorDataType,
+ colorValue);
+ });
+ }
+
/**
* Checks if the color scheme in mColorScheme matches the current system palettes.
* @param managedProfiles List of managed profiles for this user.
@@ -555,15 +558,15 @@
Resources res = userHandle.isSystem()
? mResources : mContext.createContextAsUser(userHandle, 0).getResources();
if (!(res.getColor(android.R.color.system_accent1_500, mContext.getTheme())
- == mColorScheme.getAccent1().get(6)
+ == mColorScheme.getAccent1().getS500()
&& res.getColor(android.R.color.system_accent2_500, mContext.getTheme())
- == mColorScheme.getAccent2().get(6)
+ == mColorScheme.getAccent2().getS500()
&& res.getColor(android.R.color.system_accent3_500, mContext.getTheme())
- == mColorScheme.getAccent3().get(6)
+ == mColorScheme.getAccent3().getS500()
&& res.getColor(android.R.color.system_neutral1_500, mContext.getTheme())
- == mColorScheme.getNeutral1().get(6)
+ == mColorScheme.getNeutral1().getS500()
&& res.getColor(android.R.color.system_neutral2_500, mContext.getTheme())
- == mColorScheme.getNeutral2().get(6))) {
+ == mColorScheme.getNeutral2().getS500())) {
return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 523cf68..0069bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -36,6 +36,8 @@
import android.view.WindowManager
import android.view.WindowlessWindowManager
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.LinearLightRevealEffect
@@ -57,6 +59,7 @@
@Inject
constructor(
private val context: Context,
+ private val featureFlags: FeatureFlags,
private val deviceStateManager: DeviceStateManager,
private val contentResolver: ContentResolver,
private val displayManager: DisplayManager,
@@ -81,6 +84,7 @@
private var scrimView: LightRevealScrim? = null
private var isFolded: Boolean = false
private var isUnfoldHandled: Boolean = true
+ private var overlayAddReason: AddOverlayReason? = null
private var currentRotation: Int = context.display!!.rotation
@@ -158,6 +162,8 @@
ensureInBackground()
ensureOverlayRemoved()
+ overlayAddReason = reason
+
val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
val params = getLayoutParams()
val newView =
@@ -170,11 +176,7 @@
.apply {
revealEffect = createLightRevealEffect()
isScrimOpaqueChangedListener = Consumer {}
- revealAmount =
- when (reason) {
- FOLD -> TRANSPARENT
- UNFOLD -> BLACK
- }
+ revealAmount = calculateRevealAmount()
}
newRoot.setView(newView, params)
@@ -207,6 +209,31 @@
root = newRoot
}
+ private fun calculateRevealAmount(animationProgress: Float? = null): Float {
+ val overlayAddReason = overlayAddReason ?: UNFOLD
+
+ if (animationProgress == null) {
+ // Animation progress is unknown, calculate the initial value based on the overlay
+ // add reason
+ return when (overlayAddReason) {
+ FOLD -> TRANSPARENT
+ UNFOLD -> BLACK
+ }
+ }
+
+ val showVignetteWhenFolding =
+ featureFlags.isEnabled(Flags.ENABLE_DARK_VIGNETTE_WHEN_FOLDING)
+
+ return if (!showVignetteWhenFolding && overlayAddReason == FOLD) {
+ // Do not darken the content when SHOW_VIGNETTE_WHEN_FOLDING flag is off
+ // and we are folding the device. We still add the overlay to block touches
+ // while the animation is running but the overlay is transparent.
+ TRANSPARENT
+ } else {
+ animationProgress
+ }
+ }
+
private fun getLayoutParams(): WindowManager.LayoutParams {
val params: WindowManager.LayoutParams = WindowManager.LayoutParams()
@@ -259,7 +286,7 @@
private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) {
- executeInBackground { scrimView?.revealAmount = progress }
+ executeInBackground { scrimView?.revealAmount = calculateRevealAmount(progress) }
}
override fun onTransitionFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index c570ec8..977cf0e 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -31,10 +31,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.users.EditUserInfoController;
import com.android.settingslib.users.GrantAdminDialogController;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.user.utils.MultiUserActionsEvent;
import javax.inject.Inject;
@@ -60,7 +62,7 @@
private final EditUserInfoController mEditUserInfoController;
private final IActivityManager mActivityManager;
private final ActivityStarter mActivityStarter;
-
+ private final UiEventLogger mUiEventLogger;
private Dialog mGrantAdminDialog;
private Dialog mSetupUserDialog;
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
@@ -68,11 +70,12 @@
@Inject
public CreateUserActivity(UserCreator userCreator,
EditUserInfoController editUserInfoController, IActivityManager activityManager,
- ActivityStarter activityStarter) {
+ ActivityStarter activityStarter, UiEventLogger uiEventLogger) {
mUserCreator = userCreator;
mEditUserInfoController = editUserInfoController;
mActivityManager = activityManager;
mActivityStarter = activityStarter;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -128,12 +131,22 @@
);
}
+ /**
+ * Returns dialog that allows to grant user admin rights.
+ */
private Dialog buildGrantAdminDialog() {
return new GrantAdminDialogController().createDialog(
this,
(grantAdminRights) -> {
mGrantAdminDialog.dismiss();
mGrantAdminRights = grantAdminRights;
+ if (mGrantAdminRights) {
+ mUiEventLogger.log(MultiUserActionsEvent
+ .GRANT_ADMIN_FROM_USER_SWITCHER_CREATION_DIALOG);
+ } else {
+ mUiEventLogger.log(MultiUserActionsEvent
+ .NOT_GRANT_ADMIN_FROM_USER_SWITCHER_CREATION_DIALOG);
+ }
mSetupUserDialog = createDialog();
mSetupUserDialog.show();
},
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index c0f0390..8cb4deb 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -174,7 +174,7 @@
val callback =
object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
+ override fun onUserChanging(newUser: Int, userContext: Context) {
send()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
index a9d66de..5a25a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
@@ -63,11 +63,10 @@
}
// Use broadcast instead of ShadeController, as this dialog may have started in
- // another
- // process where normal dagger bindings are not available.
+ // another process where normal dagger bindings are not available.
broadcastSender.sendBroadcastAsUser(
Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
- UserHandle.CURRENT
+ userHandle
)
context.startActivityAsUser(
diff --git a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
index ec79b6d..c1c2419 100644
--- a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
@@ -29,7 +29,11 @@
@UiEvent(doc = "Switch to Guest User tap from User Switcher.")
SWITCH_TO_GUEST_FROM_USER_SWITCHER(1267),
@UiEvent(doc = "Switch to Restricted User tap from User Switcher.")
- SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER(1268);
+ SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER(1268),
+ @UiEvent(doc = "Grant admin privileges to user on creation from User Switcher.")
+ GRANT_ADMIN_FROM_USER_SWITCHER_CREATION_DIALOG(1278),
+ @UiEvent(doc = "Not grant admin privileges to user on creation from User Switcher")
+ NOT_GRANT_ADMIN_FROM_USER_SWITCHER_CREATION_DIALOG(1279);
override fun getId(): Int {
return value
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index b61b2e6..47d505e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -16,14 +16,21 @@
package com.android.systemui.util.kotlin
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.time.SystemClockImpl
+import kotlinx.coroutines.CoroutineStart
import java.util.concurrent.atomic.AtomicReference
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
+import kotlin.math.max
/**
* Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
@@ -167,3 +174,89 @@
* Note that the returned Flow will not emit anything until [other] has emitted at least one value.
*/
fun <A> Flow<*>.sample(other: Flow<A>): Flow<A> = sample(other) { _, a -> a }
+
+/**
+ * Returns a flow that mirrors the original flow, but delays values following emitted values for the
+ * given [periodMs]. If the original flow emits more than one value during this period, only the
+ * latest value is emitted.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * flow {
+ * emit(1) // t=0ms
+ * delay(90)
+ * emit(2) // t=90ms
+ * delay(90)
+ * emit(3) // t=180ms
+ * delay(1010)
+ * emit(4) // t=1190ms
+ * delay(1010)
+ * emit(5) // t=2200ms
+ * }.throttle(1000)
+ * ```
+ *
+ * produces the following emissions at the following times
+ *
+ * ```text
+ * 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
+ * ```
+ */
+fun <T> Flow<T>.throttle(periodMs: Long): Flow<T> = this.throttle(periodMs, SystemClockImpl())
+
+/**
+ * Returns a flow that mirrors the original flow, but delays values following emitted values for the
+ * given [periodMs] as reported by the given [clock]. If the original flow emits more than one value
+ * during this period, only The latest value is emitted.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * flow {
+ * emit(1) // t=0ms
+ * delay(90)
+ * emit(2) // t=90ms
+ * delay(90)
+ * emit(3) // t=180ms
+ * delay(1010)
+ * emit(4) // t=1190ms
+ * delay(1010)
+ * emit(5) // t=2200ms
+ * }.throttle(1000)
+ * ```
+ *
+ * produces the following emissions at the following times
+ *
+ * ```text
+ * 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
+ * ```
+ */
+fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock): Flow<T> = channelFlow {
+ coroutineScope {
+ var previousEmitTimeMs = 0L
+ var delayJob: Job? = null
+ var sendJob: Job? = null
+ val outerScope = this
+
+ collect {
+ delayJob?.cancel()
+ sendJob?.join()
+ val currentTimeMs = clock.elapsedRealtime()
+ val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
+ val timeUntilNextEmit = max(0L, periodMs - timeSinceLastEmit)
+ if (timeUntilNextEmit > 0L) {
+ // We create delayJob to allow cancellation during the delay period
+ delayJob = launch {
+ delay(timeUntilNextEmit)
+ sendJob = outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ send(it)
+ previousEmitTimeMs = clock.elapsedRealtime()
+ }
+ }
+ } else {
+ send(it)
+ previousEmitTimeMs = currentTimeMs
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
index 4351afe..a0d22f3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
@@ -29,12 +29,12 @@
import android.net.Uri;
import android.os.Debug;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.util.Log;
import androidx.core.content.FileProvider;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
import com.google.android.collect.Lists;
@@ -62,13 +62,15 @@
static final String LEAK_DUMP = "leak.dump";
private final Context mContext;
+ private final UserTracker mUserTracker;
private final LeakDetector mLeakDetector;
private final String mLeakReportEmail;
@Inject
- public LeakReporter(Context context, LeakDetector leakDetector,
+ public LeakReporter(Context context, UserTracker userTracker, LeakDetector leakDetector,
@Nullable @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) {
mContext = context;
+ mUserTracker = userTracker;
mLeakDetector = leakDetector;
mLeakReportEmail = leakReportEmail;
}
@@ -111,7 +113,7 @@
getIntent(hprofFile, dumpFile),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
null,
- UserHandle.CURRENT));
+ mUserTracker.getUserHandle()));
notiMan.notify(TAG, 0, builder.build());
} catch (IOException e) {
Log.e(TAG, "Couldn't dump heap for leak", e);
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java b/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
index 0b2f004..31f35fc 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
@@ -1,15 +1,17 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.leak;
@@ -26,7 +28,27 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-public class RotationUtils {
+/**
+ * Utility class that provides device orientation.
+ *
+ * <p>Consider using {@link Surface.Rotation} or add a function that respects device aspect ratio
+ * and {@code android.internal.R.bool.config_reverseDefaultRotation}.
+ *
+ * <p>If you only care about the rotation, use {@link Surface.Rotation}, as it always gives the
+ * counter clock-wise rotation. (e.g. If you have a device that has a charging port at the bottom,
+ * rotating three times in counter clock direction will give you {@link Surface#ROTATION_270} while
+ * having the charging port on the left side of the device.)
+ *
+ * <p>If you need whether the device is in portrait or landscape (or their opposites), please add a
+ * function here that respects the device aspect ratio and
+ * {@code android.internal.R.bool.config_reverseDefaultRotation} together.
+ *
+ * <p>Note that {@code android.internal.R.bool.config_reverseDefaultRotation} does not change the
+ * winding order. In other words, the rotation order (counter clock-wise) will remain the same. It
+ * only flips the device orientation, such that portrait becomes upside down, landscape becomes
+ * seascape.
+ */
+public final class RotationUtils {
public static final int ROTATION_NONE = 0;
public static final int ROTATION_LANDSCAPE = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 1a30b0a..85fada2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -20,14 +20,18 @@
import android.net.Uri;
import android.provider.Settings;
+import com.android.systemui.settings.UserTracker;
+
import javax.inject.Inject;
class GlobalSettingsImpl implements GlobalSettings {
private final ContentResolver mContentResolver;
+ private final UserTracker mUserTracker;
@Inject
- GlobalSettingsImpl(ContentResolver contentResolver) {
+ GlobalSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
mContentResolver = contentResolver;
+ mUserTracker = userTracker;
}
@Override
@@ -36,13 +40,19 @@
}
@Override
+ public UserTracker getUserTracker() {
+ return mUserTracker;
+ }
+
+ @Override
public Uri getUriFor(String name) {
return Settings.Global.getUriFor(name);
}
@Override
public String getStringForUser(String name, int userHandle) {
- return Settings.Global.getStringForUser(mContentResolver, name, userHandle);
+ return Settings.Global.getStringForUser(mContentResolver, name,
+ getRealUserHandle(userHandle));
}
@Override
@@ -53,14 +63,16 @@
@Override
public boolean putStringForUser(String name, String value, int userHandle) {
- return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle);
+ return Settings.Global.putStringForUser(mContentResolver, name, value,
+ getRealUserHandle(userHandle));
}
@Override
public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
int userHandle, boolean overrideableByRestore) {
return Settings.Global.putStringForUser(
- mContentResolver, name, value, tag, makeDefault, userHandle, overrideableByRestore);
+ mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
+ overrideableByRestore);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index 020c234..f995436 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -20,14 +20,18 @@
import android.net.Uri;
import android.provider.Settings;
+import com.android.systemui.settings.UserTracker;
+
import javax.inject.Inject;
class SecureSettingsImpl implements SecureSettings {
private final ContentResolver mContentResolver;
+ private final UserTracker mUserTracker;
@Inject
- SecureSettingsImpl(ContentResolver contentResolver) {
+ SecureSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
mContentResolver = contentResolver;
+ mUserTracker = userTracker;
}
@Override
@@ -36,13 +40,19 @@
}
@Override
+ public UserTracker getUserTracker() {
+ return mUserTracker;
+ }
+
+ @Override
public Uri getUriFor(String name) {
return Settings.Secure.getUriFor(name);
}
@Override
public String getStringForUser(String name, int userHandle) {
- return Settings.Secure.getStringForUser(mContentResolver, name, userHandle);
+ return Settings.Secure.getStringForUser(mContentResolver, name,
+ getRealUserHandle(userHandle));
}
@Override
@@ -52,14 +62,16 @@
@Override
public boolean putStringForUser(String name, String value, int userHandle) {
- return Settings.Secure.putStringForUser(mContentResolver, name, value, userHandle);
+ return Settings.Secure.putStringForUser(mContentResolver, name, value,
+ getRealUserHandle(userHandle));
}
@Override
public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
int userHandle, boolean overrideableByRestore) {
return Settings.Secure.putStringForUser(
- mContentResolver, name, value, tag, makeDefault, userHandle, overrideableByRestore);
+ mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
+ overrideableByRestore);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
index 1bf5f07..b6846a3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
@@ -22,8 +22,11 @@
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.UserHandle;
import android.provider.Settings;
+import com.android.systemui.settings.UserTracker;
+
/**
* Used to interact with Settings.Secure, Settings.Global, and Settings.System.
*
@@ -46,6 +49,11 @@
ContentResolver getContentResolver();
/**
+ * Returns that {@link UserTracker} this instance was constructed with.
+ */
+ UserTracker getUserTracker();
+
+ /**
* Returns the user id for the associated {@link ContentResolver}.
*/
default int getUserId() {
@@ -53,6 +61,17 @@
}
/**
+ * Returns the actual current user handle when querying with the current user. Otherwise,
+ * returns the passed in user id.
+ */
+ default int getRealUserHandle(int userHandle) {
+ if (userHandle != UserHandle.USER_CURRENT) {
+ return userHandle;
+ }
+ return getUserTracker().getUserId();
+ }
+
+ /**
* Construct the content URI for a particular name/value pair,
* useful for monitoring changes with a ContentObserver.
* @param name to look up in the table
@@ -84,18 +103,18 @@
*
* Implicitly calls {@link #getUriFor(String)} on the passed in name.
*/
- default void registerContentObserver(String name, boolean notifyForDescendents,
+ default void registerContentObserver(String name, boolean notifyForDescendants,
ContentObserver settingsObserver) {
- registerContentObserver(getUriFor(name), notifyForDescendents, settingsObserver);
+ registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
}
/**
* Convenience wrapper around
* {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
*/
- default void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ default void registerContentObserver(Uri uri, boolean notifyForDescendants,
ContentObserver settingsObserver) {
- registerContentObserverForUser(uri, notifyForDescendents, settingsObserver, getUserId());
+ registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
}
/**
@@ -127,10 +146,10 @@
* Implicitly calls {@link #getUriFor(String)} on the passed in name.
*/
default void registerContentObserverForUser(
- String name, boolean notifyForDescendents, ContentObserver settingsObserver,
+ String name, boolean notifyForDescendants, ContentObserver settingsObserver,
int userHandle) {
registerContentObserverForUser(
- getUriFor(name), notifyForDescendents, settingsObserver, userHandle);
+ getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
}
/**
@@ -138,10 +157,10 @@
* {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
*/
default void registerContentObserverForUser(
- Uri uri, boolean notifyForDescendents, ContentObserver settingsObserver,
+ Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
int userHandle) {
getContentResolver().registerContentObserver(
- uri, notifyForDescendents, settingsObserver, userHandle);
+ uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
}
/** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index 0b8257d..561495e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -19,7 +19,6 @@
import android.annotation.UserIdInt
import android.database.ContentObserver
-import android.os.UserHandle
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -29,8 +28,8 @@
/** Returns a flow of [Unit] that is invoked each time that content is updated. */
fun SettingsProxy.observerFlow(
+ @UserIdInt userId: Int,
vararg names: String,
- @UserIdInt userId: Int = UserHandle.USER_CURRENT,
): Flow<Unit> {
return conflatedCallbackFlow {
val observer =
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index 0dbb76f..fba7ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -20,14 +20,18 @@
import android.net.Uri;
import android.provider.Settings;
+import com.android.systemui.settings.UserTracker;
+
import javax.inject.Inject;
class SystemSettingsImpl implements SystemSettings {
private final ContentResolver mContentResolver;
+ private final UserTracker mUserTracker;
@Inject
- SystemSettingsImpl(ContentResolver contentResolver) {
+ SystemSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
mContentResolver = contentResolver;
+ mUserTracker = userTracker;
}
@Override
@@ -36,13 +40,19 @@
}
@Override
+ public UserTracker getUserTracker() {
+ return mUserTracker;
+ }
+
+ @Override
public Uri getUriFor(String name) {
return Settings.System.getUriFor(name);
}
@Override
public String getStringForUser(String name, int userHandle) {
- return Settings.System.getStringForUser(mContentResolver, name, userHandle);
+ return Settings.System.getStringForUser(mContentResolver, name,
+ getRealUserHandle(userHandle));
}
@Override
@@ -52,7 +62,8 @@
@Override
public boolean putStringForUser(String name, String value, int userHandle) {
- return Settings.System.putStringForUser(mContentResolver, name, value, userHandle);
+ return Settings.System.putStringForUser(mContentResolver, name, value,
+ getRealUserHandle(userHandle));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index f71d988..a453726 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -25,6 +25,7 @@
import android.provider.Settings;
import android.view.WindowManager.LayoutParams;
+import com.android.internal.R;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoMode;
@@ -55,7 +56,7 @@
public static final String VOLUME_UP_SILENT = "sysui_volume_up_silent";
public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb";
- public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false;
+ private final boolean mDefaultVolumeDownToEnterSilent;
public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
@@ -72,12 +73,7 @@
private final KeyguardViewMediator mKeyguardViewMediator;
private final ActivityStarter mActivityStarter;
private VolumeDialog mDialog;
- private VolumePolicy mVolumePolicy = new VolumePolicy(
- DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent
- DEFAULT_VOLUME_UP_TO_EXIT_SILENT, // volumeUpToExitSilent
- DEFAULT_DO_NOT_DISTURB_WHEN_SILENT, // doNotDisturbWhenSilent
- 400 // vibrateToSilentDebounce
- );
+ private VolumePolicy mVolumePolicy;
@Inject
public VolumeDialogComponent(
@@ -107,7 +103,20 @@
mDialog = dialog;
mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
}).build();
+
+
+ mDefaultVolumeDownToEnterSilent = mContext.getResources()
+ .getBoolean(R.bool.config_volume_down_to_enter_silent);
+
+ mVolumePolicy = new VolumePolicy(
+ mDefaultVolumeDownToEnterSilent, // volumeDownToEnterSilent
+ DEFAULT_VOLUME_UP_TO_EXIT_SILENT, // volumeUpToExitSilent
+ DEFAULT_DO_NOT_DISTURB_WHEN_SILENT, // doNotDisturbWhenSilent
+ 400 // vibrateToSilentDebounce
+ );
+
applyConfiguration();
+
tunerService.addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
VOLUME_SILENT_DO_NOT_DISTURB);
demoModeController.addCallback(this);
@@ -121,7 +130,7 @@
if (VOLUME_DOWN_SILENT.equals(key)) {
volumeDownToEnterSilent =
- TunerService.parseIntegerSwitch(newValue, DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT);
+ TunerService.parseIntegerSwitch(newValue, mDefaultVolumeDownToEnterSilent);
} else if (VOLUME_UP_SILENT.equals(key)) {
volumeUpToExitSilent =
TunerService.parseIntegerSwitch(newValue, DEFAULT_VOLUME_UP_TO_EXIT_SILENT);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 2fc8b03..d39a53d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -47,7 +47,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.Condition;
@@ -70,6 +69,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
@@ -134,6 +134,7 @@
private final CaptioningManager mCaptioningManager;
private final KeyguardManager mKeyguardManager;
private final ActivityManager mActivityManager;
+ private final UserTracker mUserTracker;
protected C mCallbacks = new C();
private final State mState = new State();
protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
@@ -181,6 +182,7 @@
CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
+ UserTracker userTracker,
DumpManager dumpManager
) {
mContext = context.getApplicationContext();
@@ -210,6 +212,7 @@
mCaptioningManager = captioningManager;
mKeyguardManager = keyguardManager;
mActivityManager = activityManager;
+ mUserTracker = userTracker;
dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
boolean accessibilityVolumeStreamActive = accessibilityManager
@@ -372,7 +375,7 @@
if (System.currentTimeMillis() - mLastToggledRingerOn < TOUCH_FEEDBACK_TIMEOUT_MS) {
try {
mAudioService.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD,
- UserHandle.USER_CURRENT);
+ mUserTracker.getUserId());
} catch (RemoteException e) {
// ignore
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 4cbc709..4da5d49 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -273,7 +273,7 @@
};
mSecureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT),
+ Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
false /* notifyForDescendants */,
mDefaultPaymentAppObserver,
UserHandle.USER_ALL);
@@ -293,7 +293,7 @@
};
mSecureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(QuickAccessWalletClientImpl.SETTING_KEY),
+ QuickAccessWalletClientImpl.SETTING_KEY,
false /* notifyForDescendants */,
mWalletPreferenceObserver,
UserHandle.USER_ALL);
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
new file mode 100644
index 0000000..7b8235a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.systemui.wallet.controller
+
+import android.Manifest
+import android.content.Context
+import android.content.IntentFilter
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
+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.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.shareIn
+
+@SysUISingleton
+class WalletContextualSuggestionsController
+@Inject
+constructor(
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ private val walletController: QuickAccessWalletController,
+ broadcastDispatcher: BroadcastDispatcher,
+ featureFlags: FeatureFlags
+) {
+ private val allWalletCards: Flow<List<WalletCard>> =
+ if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+ conflatedCallbackFlow {
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
+ trySendWithFailureLogging(response.walletCards, TAG)
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
+ trySendWithFailureLogging(emptyList<WalletCard>(), TAG)
+ }
+ }
+
+ walletController.setupWalletChangeObservers(
+ callback,
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+
+ awaitClose {
+ walletController.unregisterWalletChangeObservers(
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ }
+ }
+ } else {
+ emptyFlow()
+ }
+
+ private val contextualSuggestionsCardIds: Flow<Set<String>> =
+ if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(ACTION_UPDATE_WALLET_CONTEXTUAL_SUGGESTIONS),
+ permission = Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE,
+ flags = Context.RECEIVER_EXPORTED
+ ) { intent, _ ->
+ if (intent.hasExtra(UPDATE_CARD_IDS_EXTRA)) {
+ intent.getStringArrayListExtra(UPDATE_CARD_IDS_EXTRA).toSet()
+ } else {
+ emptySet()
+ }
+ }
+ } else {
+ emptyFlow()
+ }
+
+ val contextualSuggestionCards: Flow<List<WalletCard>> =
+ combine(allWalletCards, contextualSuggestionsCardIds) { cards, ids ->
+ cards.filter { card -> ids.contains(card.cardId) }
+ }
+ .shareIn(applicationCoroutineScope, replay = 1, started = SharingStarted.Eagerly)
+
+ companion object {
+ private const val ACTION_UPDATE_WALLET_CONTEXTUAL_SUGGESTIONS =
+ "com.android.systemui.wallet.UPDATE_CONTEXTUAL_SUGGESTIONS"
+
+ private const val UPDATE_CARD_IDS_EXTRA = "cardIds"
+
+ private const val TAG = "WalletSuggestions"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 51742990..31df3bc 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -28,7 +28,6 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Trace;
-import android.os.UserHandle;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.Surface;
@@ -39,6 +38,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.FileDescriptor;
@@ -60,6 +60,8 @@
private volatile int mPages = 1;
private boolean mPagesComputed = false;
+ private final UserTracker mUserTracker;
+
// used to handle WallpaperService messages (e.g. DO_ATTACH, MSG_UPDATE_SURFACE)
// and to receive WallpaperService callbacks (e.g. onCreateEngine, onSurfaceRedrawNeeded)
private HandlerThread mWorker;
@@ -72,9 +74,11 @@
private static final int DELAY_UNLOAD_BITMAP = 2000;
@Inject
- public ImageWallpaper(@Background DelayableExecutor backgroundExecutor) {
+ public ImageWallpaper(@Background DelayableExecutor backgroundExecutor,
+ UserTracker userTracker) {
super();
mBackgroundExecutor = backgroundExecutor;
+ mUserTracker = userTracker;
}
@Override
@@ -310,7 +314,7 @@
boolean loadSuccess = false;
Bitmap bitmap;
try {
- bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
+ bitmap = mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
if (bitmap != null
&& bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
throw new RuntimeException("Wallpaper is too large to draw!");
@@ -322,9 +326,9 @@
// default wallpaper can't be loaded.
Log.w(TAG, "Unable to load wallpaper!", exception);
mWallpaperManager.clearWallpaper(
- WallpaperManager.FLAG_SYSTEM, UserHandle.USER_CURRENT);
+ WallpaperManager.FLAG_SYSTEM, mUserTracker.getUserId());
try {
- bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
+ bitmap = mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
} catch (RuntimeException | OutOfMemoryError e) {
Log.w(TAG, "Unable to load default wallpaper!", e);
bitmap = null;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 9a9acf3..a4180fd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -29,7 +29,6 @@
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
@@ -275,7 +274,7 @@
ArgumentCaptor<ContentObserver> observerCaptor =
ArgumentCaptor.forClass(ContentObserver.class);
mController.init();
- verify(mSecureSettings).registerContentObserverForUser(any(Uri.class),
+ verify(mSecureSettings).registerContentObserverForUser(any(String.class),
anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
ContentObserver observer = observerCaptor.getValue();
mExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
index 7aa4763..4a5c1be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
@@ -22,14 +22,16 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-import android.os.UserHandle;
+import android.app.ActivityManager;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Rule;
@@ -42,11 +44,14 @@
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class AccessibilityButtonModeObserverTest extends SysuiTestCase {
+ private static final int MY_USER_ID = ActivityManager.getCurrentUser();
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private AccessibilityButtonModeObserver.ModeChangedListener mListener;
private AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
@@ -56,10 +61,12 @@
@Before
public void setUp() {
+ when(mUserTracker.getUserId()).thenReturn(MY_USER_ID);
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
- mAccessibilityButtonModeObserver = new AccessibilityButtonModeObserver(mContext);
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, MY_USER_ID);
+ mAccessibilityButtonModeObserver = new AccessibilityButtonModeObserver(mContext,
+ mUserTracker);
}
@Test
@@ -67,7 +74,7 @@
mAccessibilityButtonModeObserver.addListener(mListener);
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
- UserHandle.USER_CURRENT);
+ MY_USER_ID);
mAccessibilityButtonModeObserver.mContentObserver.onChange(false);
@@ -80,7 +87,7 @@
mAccessibilityButtonModeObserver.removeListener(mListener);
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
- UserHandle.USER_CURRENT);
+ MY_USER_ID);
mAccessibilityButtonModeObserver.mContentObserver.onChange(false);
@@ -91,7 +98,7 @@
public void getCurrentAccessibilityButtonMode_expectedValue() {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
- UserHandle.USER_CURRENT);
+ MY_USER_ID);
final int actualValue =
mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
index 4145437..a5a7a4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
@@ -21,14 +21,16 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-import android.os.UserHandle;
+import android.app.ActivityManager;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Rule;
@@ -42,11 +44,14 @@
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class AccessibilityButtonTargetsObserverTest extends SysuiTestCase {
+ private static final int MY_USER_ID = ActivityManager.getCurrentUser();
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private AccessibilityButtonTargetsObserver.TargetsChangedListener mListener;
private AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
@@ -55,7 +60,9 @@
@Before
public void setUp() {
- mAccessibilityButtonTargetsObserver = new AccessibilityButtonTargetsObserver(mContext);
+ when(mUserTracker.getUserId()).thenReturn(MY_USER_ID);
+ mAccessibilityButtonTargetsObserver = new AccessibilityButtonTargetsObserver(mContext,
+ mUserTracker);
}
@Test
@@ -63,7 +70,7 @@
mAccessibilityButtonTargetsObserver.addListener(mListener);
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- UserHandle.USER_CURRENT);
+ MY_USER_ID);
mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false);
@@ -76,7 +83,7 @@
mAccessibilityButtonTargetsObserver.removeListener(mListener);
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- UserHandle.USER_CURRENT);
+ MY_USER_ID);
mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false);
@@ -87,7 +94,7 @@
public void getCurrentAccessibilityButtonTargets_expectedValue() {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- UserHandle.USER_CURRENT);
+ MY_USER_ID);
final String actualValue =
mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index 58b4af4..47dff51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -73,6 +74,8 @@
private IRemoteMagnificationAnimationCallback mAnimationCallback;
@Mock
private OverviewProxyService mOverviewProxyService;
+ @Mock
+ private SecureSettings mSecureSettings;
private IWindowMagnificationConnection mIWindowMagnificationConnection;
private WindowMagnification mWindowMagnification;
@@ -88,7 +91,7 @@
any(IWindowMagnificationConnection.class));
mWindowMagnification = new WindowMagnification(getContext(),
getContext().getMainThreadHandler(), mCommandQueue,
- mModeSwitchesController, mSysUiState, mOverviewProxyService);
+ mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings);
mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
index 41fd2b3..9c601a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
@@ -18,18 +18,20 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.ActivityManager;
import android.content.Context;
-import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
/** Test for {@link SecureSettingsContentObserver}. */
@RunWith(AndroidTestingRunner.class)
@@ -40,7 +42,9 @@
@Before
public void setUpObserver() {
- mTestObserver = new FakeSecureSettingsContentObserver(mContext,
+ UserTracker userTracker = Mockito.mock(UserTracker.class);
+ Mockito.when(userTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ mTestObserver = new FakeSecureSettingsContentObserver(mContext, userTracker,
Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
}
@@ -57,7 +61,7 @@
@Test
public void checkValue() {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 1, UserHandle.USER_CURRENT);
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 1, ActivityManager.getCurrentUser());
assertThat(mTestObserver.getSettingsValue()).isEqualTo("1");
}
@@ -66,9 +70,9 @@
private static class FakeSecureSettingsContentObserver extends
SecureSettingsContentObserver<Object> {
- protected FakeSecureSettingsContentObserver(Context context,
+ protected FakeSecureSettingsContentObserver(Context context, UserTracker userTracker,
String secureSettingsKey) {
- super(context, secureSettingsKey);
+ super(context, userTracker, secureSettingsKey);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 0850330..84ea54c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -97,6 +98,8 @@
IRemoteMagnificationAnimationCallback mAnimationCallback2;
@Mock(answer = Answers.RETURNS_SELF)
SysUiState mSysUiState;
+ @Mock
+ SecureSettings mSecureSettings;
private SpyWindowMagnificationController mController;
private WindowMagnificationController mSpyController;
private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
@@ -121,7 +124,7 @@
mController = new SpyWindowMagnificationController(mContext, mHandler,
mWindowMagnificationAnimationController,
mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
- mWindowMagnifierCallback, mSysUiState);
+ mWindowMagnifierCallback, mSysUiState, mSecureSettings);
mSpyController = mController.getSpyController();
}
@@ -761,7 +764,8 @@
WindowMagnificationAnimationController animationController,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
- WindowMagnifierCallback callback, SysUiState sysUiState) {
+ WindowMagnifierCallback callback, SysUiState sysUiState,
+ SecureSettings secureSettings) {
super(
context,
handler,
@@ -771,7 +775,8 @@
transaction,
callback,
sysUiState,
- WindowManagerGlobal::getWindowSession);
+ WindowManagerGlobal::getWindowSession,
+ secureSettings);
mSpyController = Mockito.mock(WindowMagnificationController.class);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 9d39a8c..89ab835 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -34,8 +34,11 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.returnsSecondArg;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
@@ -85,6 +88,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
import com.android.systemui.util.leak.ReferenceTestUtils;
+import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.utils.os.FakeHandler;
import com.google.common.util.concurrent.AtomicDouble;
@@ -122,6 +126,8 @@
IRemoteMagnificationAnimationCallback mAnimationCallback;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ @Mock
+ private SecureSettings mSecureSettings;
private Handler mHandler;
private TestableWindowManager mWindowManager;
@@ -157,6 +163,10 @@
}).when(mSfVsyncFrameProvider).postFrameCallback(
any(FrameCallback.class));
mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
+ returnsSecondArg());
+ when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
+ returnsSecondArg());
mResources = getContext().getOrCreateTestableResources().getResources();
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
@@ -171,7 +181,8 @@
mTransaction,
mWindowMagnifierCallback,
mSysUiState,
- () -> mWindowSessionSpy);
+ () -> mWindowSessionSpy,
+ mSecureSettings);
verify(mMirrorWindowControl).setWindowDelegate(
any(MirrorWindowControl.MirrorWindowDelegate.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 2f94b69..d803075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -38,6 +38,7 @@
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -61,6 +62,8 @@
@Mock
private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
+ private SecureSettings mSecureSettings;
+ @Mock
private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
private TestableWindowManager mWindowManager;
private WindowMagnificationSettings mWindowMagnificationSettings;
@@ -77,7 +80,8 @@
mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
- mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider);
+ mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider,
+ mSecureSettings);
mSettingView = mWindowMagnificationSettings.getSettingView();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index e1bd25b..14b00ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -70,6 +71,8 @@
private IWindowMagnificationConnectionCallback mConnectionCallback;
@Mock
private OverviewProxyService mOverviewProxyService;
+ @Mock
+ private SecureSettings mSecureSettings;
private CommandQueue mCommandQueue;
private WindowMagnification mWindowMagnification;
@@ -90,7 +93,7 @@
mCommandQueue = new CommandQueue(getContext());
mWindowMagnification = new WindowMagnification(getContext(),
getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
- mSysUiState, mOverviewProxyService);
+ mSysUiState, mOverviewProxyService, mSecureSettings);
mWindowMagnification.start();
final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 77d38c5..15a3145 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.display.DisplayManager;
@@ -46,6 +47,7 @@
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -54,6 +56,8 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -79,9 +83,12 @@
private KeyguardUpdateMonitorCallback mKeyguardCallback;
private int mLastButtonMode;
private String mLastButtonTargets;
+ @Mock
+ private SecureSettings mSecureSettings;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mContextWrapper = new ContextWrapper(mContext) {
@Override
public Context createContextAsUser(UserHandle user, int flags) {
@@ -150,7 +157,7 @@
public void onKeyguardVisibilityChanged_showing_destroyWidget() {
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper);
+ mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserUnlocked();
@@ -176,7 +183,7 @@
final int fakeUserId = 1;
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper);
+ mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserSwitching(fakeUserId);
@@ -189,7 +196,7 @@
final int fakeUserId = 1;
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper);
+ mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserUnlocked();
mKeyguardCallback.onKeyguardVisibilityChanged(true);
@@ -219,7 +226,7 @@
public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() {
Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- UserHandle.USER_CURRENT);
+ ActivityManager.getCurrentUser());
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -230,7 +237,7 @@
@Test
public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() {
Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -242,7 +249,7 @@
public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() {
Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- UserHandle.USER_CURRENT);
+ ActivityManager.getCurrentUser());
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -253,7 +260,7 @@
@Test
public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() {
Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -265,7 +272,7 @@
public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() {
Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- UserHandle.USER_CURRENT);
+ ActivityManager.getCurrentUser());
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -277,7 +284,7 @@
public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() {
Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- UserHandle.USER_CURRENT);
+ ActivityManager.getCurrentUser());
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged("");
@@ -289,7 +296,7 @@
public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() {
Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -301,7 +308,7 @@
public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() {
Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged("");
@@ -352,7 +359,7 @@
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver,
- mKeyguardUpdateMonitor, featureFlags);
+ mKeyguardUpdateMonitor, featureFlags, mSecureSettings);
controller.init();
return controller;
@@ -361,10 +368,10 @@
private void enableAccessibilityFloatingMenuConfig() {
Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- UserHandle.USER_CURRENT);
+ ActivityManager.getCurrentUser());
Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- UserHandle.USER_CURRENT);
+ ActivityManager.getCurrentUser());
}
private void captureKeyguardUpdateMonitorCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
index 558261b..04345fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
@@ -31,6 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -55,6 +56,8 @@
@Mock
private AccessibilityManager mAccessibilityManager;
+ @Mock
+ private SecureSettings mSecureSettings;
private AccessibilityFloatingMenuView mMenuView;
private AccessibilityFloatingMenu mMenu;
@@ -69,7 +72,7 @@
final Position position = new Position(0, 0);
mMenuView = new AccessibilityFloatingMenuView(mContext, position);
- mMenu = new AccessibilityFloatingMenu(mContext, mMenuView);
+ mMenu = new AccessibilityFloatingMenu(mContext, mSecureSettings, mMenuView);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index a4b9b08..f6ca938 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.floatingmenu;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -27,6 +28,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.bubbles.DismissView;
import org.junit.Before;
@@ -54,7 +56,8 @@
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
+ mock(SecureSettings.class));
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 7356184..3a8bcd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -77,7 +78,8 @@
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
+ mock(SecureSettings.class));
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
mViewPropertyAnimator = spy(mMenuView.animate());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
index 06340af..1faa8ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
@@ -31,6 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -57,6 +58,8 @@
@Mock
private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged;
+ @Mock
+ private SecureSettings mSecureSettings;
private MenuInfoRepository mMenuInfoRepository;
private final List<String> mShortcutTargets = new ArrayList<>();
@@ -69,7 +72,7 @@
anyInt());
mMenuInfoRepository = new MenuInfoRepository(mContext, mAccessibilityManager,
- mMockSettingsContentsChanged);
+ mMockSettingsContentsChanged, mSecureSettings);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index f17b1cf..1b0a10e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -39,6 +39,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Rule;
@@ -58,7 +59,8 @@
@Mock
private AccessibilityManager mAccessibilityManager;
-
+ @Mock
+ private SecureSettings mSecureSettings;
@Mock
private DismissAnimationController.DismissCallback mStubDismissCallback;
@@ -73,7 +75,8 @@
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
+ mSecureSettings);
final int halfScreenHeight =
stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index ed9562d..d4efbe4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -39,6 +39,7 @@
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.bubbles.DismissView;
import org.junit.After;
@@ -78,7 +79,8 @@
@Before
public void setUp() throws Exception {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
+ mock(SecureSettings.class));
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
windowManager);
mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index dd7ce0e..31824ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Rule;
@@ -63,6 +64,9 @@
private AccessibilityManager mAccessibilityManager;
@Mock
+ private SecureSettings mSecureSettings;
+
+ @Mock
private WindowMetrics mWindowMetrics;
private MenuViewLayerController mMenuViewLayerController;
@@ -77,7 +81,7 @@
when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340));
when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets());
mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager,
- mAccessibilityManager);
+ mAccessibilityManager, mSecureSettings);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 428a00a..728ea1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -22,7 +22,6 @@
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
-import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
import static com.google.common.truth.Truth.assertThat;
@@ -54,6 +53,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -100,6 +100,9 @@
private IAccessibilityFloatingMenu mFloatingMenu;
@Mock
+ private SecureSettings mSecureSettings;
+
+ @Mock
private WindowManager mStubWindowManager;
@Mock
@@ -114,7 +117,7 @@
doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager,
- mFloatingMenu);
+ mFloatingMenu, mSecureSettings);
mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
mMenuAnimationController = mMenuView.getMenuAnimationController();
@@ -170,16 +173,10 @@
@Test
public void tiggerDismissMenuAction_matchA11yButtonTargetsResult() {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- MAGNIFICATION_COMPONENT_NAME.flattenToString(), UserHandle.USER_CURRENT);
-
mMenuViewLayer.mDismissMenuAction.run();
- final String value =
- Settings.Secure.getStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
-
- assertThat(value).isEqualTo("");
+ verify(mSecureSettings).putStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
+ UserHandle.USER_CURRENT);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 5a1a6db..5cd0fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -35,6 +36,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
@@ -68,7 +70,8 @@
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
+ mock(SecureSettings.class));
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 7c9d22f..b9a952a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -53,6 +53,7 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -99,6 +100,7 @@
@Mock private lateinit var unlockedScreenOffAnimationController:
UnlockedScreenOffAnimationController
@Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
+ @Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
@@ -138,10 +140,10 @@
context, fingerprintManager, inflater, windowManager, accessibilityManager,
statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
- configurationController, keyguardStateController,
- unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
+ configurationController, keyguardStateController, unlockedScreenOffAnimationController,
+ udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
- primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable,
+ primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index d7b7a7d..dd7082a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -98,6 +98,7 @@
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
@@ -209,6 +210,8 @@
private SessionTracker mSessionTracker;
@Mock
private AlternateBouncerInteractor mAlternateBouncerInteractor;
+ @Mock
+ private SecureSettings mSecureSettings;
// Capture listeners so that they can be used to send events
@Captor
@@ -302,7 +305,7 @@
mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
- mAlternateBouncerInteractor);
+ mAlternateBouncerInteractor, mSecureSettings);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 262b4b8..80c3e5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -69,6 +70,8 @@
lateinit var cameraIntents: CameraIntentsWrapper
@Mock
lateinit var contentResolver: ContentResolver
+ @Mock
+ lateinit var userTracker: UserTracker
private lateinit var underTest: CameraGestureHelper
@@ -96,6 +99,7 @@
cameraIntents = cameraIntents,
contentResolver = contentResolver,
uiExecutor = MoreExecutors.directExecutor(),
+ userTracker = userTracker,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d159714..d6cafcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -16,18 +16,23 @@
package com.android.systemui.charging
+import android.graphics.Rect
import android.testing.AndroidTestingRunner
+import android.view.Surface
import android.view.View
import android.view.WindowManager
+import android.view.WindowMetrics
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.surfaceeffects.ripple.RippleView
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
@@ -35,12 +40,12 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mock
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -54,6 +59,7 @@
@Mock private lateinit var rippleView: RippleView
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var windowMetrics: WindowMetrics
private val systemClock = FakeSystemClock()
@Before
@@ -66,6 +72,9 @@
rippleView.setupShader()
controller.rippleView = rippleView // Replace the real ripple view with a mock instance
controller.registerCallbacks()
+
+ `when`(windowMetrics.bounds).thenReturn(Rect(0, 0, 100, 100))
+ `when`(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
}
@Test
@@ -164,4 +173,63 @@
verify(rippleView, never()).addOnAttachStateChangeListener(attachListenerCaptor.capture())
verify(windowManager, never()).addView(eq(rippleView), any<WindowManager.LayoutParams>())
}
+
+ @Test
+ fun testRipple_layoutsCorrectly() {
+ // Sets the correct ripple size.
+ val width = 100
+ val height = 200
+ whenever(windowMetrics.bounds).thenReturn(Rect(0, 0, width, height))
+
+ // Trigger ripple.
+ val captor = ArgumentCaptor
+ .forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ captor.value.onBatteryLevelChanged(
+ /* unusedBatteryLevel= */ 0,
+ /* plugged in= */ true,
+ /* charging= */ false)
+
+ val attachListenerCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+ verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
+ verify(windowManager).addView(eq(rippleView), any<WindowManager.LayoutParams>())
+
+ val runnableCaptor =
+ ArgumentCaptor.forClass(Runnable::class.java)
+ attachListenerCaptor.value.onViewAttachedToWindow(rippleView)
+ verify(rippleView).startRipple(runnableCaptor.capture())
+
+ // Verify size and center position.
+ val maxSize = 400f // Double the max value between width and height.
+ verify(rippleView).setMaxSize(maxWidth = maxSize, maxHeight = maxSize)
+
+ val normalizedPortPosX =
+ context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_x)
+ val normalizedPortPosY =
+ context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
+ val expectedCenterX: Float
+ val expectedCenterY: Float
+ when (context.display.rotation) {
+ Surface.ROTATION_90 -> {
+ expectedCenterX = width * normalizedPortPosY
+ expectedCenterY = height * (1 - normalizedPortPosX)
+ }
+ Surface.ROTATION_180 -> {
+ expectedCenterX = width * (1 - normalizedPortPosX)
+ expectedCenterY = height * (1 - normalizedPortPosY)
+ }
+ Surface.ROTATION_270 -> {
+ expectedCenterX = width * (1 - normalizedPortPosY)
+ expectedCenterY = height * normalizedPortPosX
+ }
+ else -> { // Surface.ROTATION_0
+ expectedCenterX = width * normalizedPortPosX
+ expectedCenterY = height * normalizedPortPosY
+ }
+ }
+
+ verify(rippleView).setCenter(expectedCenterX, expectedCenterY)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 5c2b153..af027e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -36,6 +37,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.doze.DozeMachine.State;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Test;
@@ -48,6 +50,7 @@
@RunWithLooper
public class DozeDockHandlerTest extends SysuiTestCase {
@Mock private DozeMachine mMachine;
+ @Mock private UserTracker mUserTracker;
private AmbientDisplayConfiguration mConfig;
private DockManagerFake mDockManagerFake;
private DozeDockHandler mDockHandler;
@@ -57,9 +60,10 @@
MockitoAnnotations.initMocks(this);
mConfig = DozeConfigurationUtil.createMockConfig();
mDockManagerFake = spy(new DockManagerFake());
- mDockHandler = new DozeDockHandler(mConfig, mDockManagerFake);
+ mDockHandler = new DozeDockHandler(mConfig, mDockManagerFake, mUserTracker);
mDockHandler.setDozeMachine(mMachine);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
when(mMachine.getState()).thenReturn(State.DOZE_AOD);
doReturn(true).when(mConfig).alwaysOnEnabled(anyInt());
mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 5bbd810..a636b7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -45,6 +45,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
@@ -57,6 +58,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.wakelock.WakeLockFake;
@@ -85,6 +87,8 @@
private DozeMachine.Part mPartMock;
@Mock
private DozeMachine.Part mAnotherPartMock;
+ @Mock
+ private UserTracker mUserTracker;
private DozeServiceFake mServiceFake;
private WakeLockFake mWakeLockFake;
private AmbientDisplayConfiguration mAmbientDisplayConfigMock;
@@ -97,6 +101,7 @@
mAmbientDisplayConfigMock = mock(AmbientDisplayConfiguration.class);
when(mDockManager.isDocked()).thenReturn(false);
when(mDockManager.isHidden()).thenReturn(false);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
mMachine = new DozeMachine(mServiceFake,
mAmbientDisplayConfigMock,
@@ -105,7 +110,8 @@
mDozeLog,
mDockManager,
mHost,
- new DozeMachine.Part[]{mPartMock, mAnotherPartMock});
+ new DozeMachine.Part[]{mPartMock, mAnotherPartMock},
+ mUserTracker);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 03827da..3af444a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -34,6 +34,7 @@
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.reset;
@@ -57,6 +58,7 @@
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.FakeSensorManager;
+import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -94,6 +96,8 @@
DevicePostureController mDevicePostureController;
@Mock
DozeLog mDozeLog;
+ @Mock
+ SystemSettings mSystemSettings;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
@@ -102,9 +106,8 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- Settings.System.putIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS, DEFAULT_BRIGHTNESS,
- UserHandle.USER_CURRENT);
+ when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
+ eq(UserHandle.USER_CURRENT))).thenReturn(DEFAULT_BRIGHTNESS);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -131,7 +134,8 @@
mWakefulnessLifecycle,
mDozeParameters,
mDevicePostureController,
- mDozeLog);
+ mDozeLog,
+ mSystemSettings);
}
@Test
@@ -157,11 +161,10 @@
}
@Test
- public void testAod_usesLightSensorRespectingUserSetting() throws Exception {
+ public void testAod_usesLightSensorRespectingUserSetting() {
int maxBrightness = 3;
- Settings.System.putIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS, maxBrightness,
- UserHandle.USER_CURRENT);
+ when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
+ eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
assertEquals(maxBrightness, mServiceFake.screenBrightness);
@@ -238,7 +241,8 @@
mWakefulnessLifecycle,
mDozeParameters,
mDevicePostureController,
- mDozeLog);
+ mDozeLog,
+ mSystemSettings);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
reset(mDozeHost);
@@ -275,7 +279,8 @@
mWakefulnessLifecycle,
mDozeParameters,
mDevicePostureController,
- mDozeLog);
+ mDozeLog,
+ mSystemSettings);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
@@ -306,7 +311,8 @@
mWakefulnessLifecycle,
mDozeParameters,
mDevicePostureController,
- mDozeLog);
+ mDozeLog,
+ mSystemSettings);
// GIVEN the device is in AOD
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
@@ -344,7 +350,8 @@
mWakefulnessLifecycle,
mDozeParameters,
mDevicePostureController,
- mDozeLog);
+ mDozeLog,
+ mSystemSettings);
// GIVEN device is in AOD
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
@@ -386,7 +393,8 @@
mWakefulnessLifecycle,
mDozeParameters,
mDevicePostureController,
- mDozeLog);
+ mDozeLog,
+ mSystemSettings);
verify(mDevicePostureController).addCallback(postureCallbackCaptor.capture());
// GIVEN device is in AOD
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index b6da649..a88a8e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -37,11 +37,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -122,11 +122,13 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
.thenReturn(new String[]{"tapSensor"});
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
- when(mAmbientDisplayConfiguration.enabled(UserHandle.USER_CURRENT)).thenReturn(true);
+ when(mAmbientDisplayConfiguration.enabled(ActivityManager.getCurrentUser())).thenReturn(
+ true);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
index 32b9945..9064470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
@@ -36,6 +36,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
@@ -43,6 +44,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import org.junit.After;
@@ -73,6 +75,8 @@
private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@Mock
private BiometricUnlockController mBiometricUnlockController;
+ @Mock
+ private UserTracker mUserTracker;
@Mock
private DozeMachine mDozeMachine;
@@ -89,12 +93,14 @@
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
when(mBiometricUnlockController.hasPendingAuthentication()).thenReturn(false);
when(mDozeHost.isProvisioned()).thenReturn(true);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
mDozeSuppressor = new DozeSuppressor(
mDozeHost,
mConfig,
mDozeLog,
- mBiometricUnlockControllerLazy);
+ mBiometricUnlockControllerLazy,
+ mUserTracker);
mDozeSuppressor.setDozeMachine(mDozeMachine);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 8f97026..b73bfc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -275,6 +275,32 @@
}
@Test
+ public void testOnEndDream() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback,
+ LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ // Verify view added.
+ verify(mWindowManager).addView(mViewCaptor.capture(), any());
+
+ // Service destroyed.
+ mService.onEndDream();
+ mMainExecutor.runAllReady();
+
+ // Verify view removed.
+ verify(mWindowManager).removeView(mViewCaptor.getValue());
+
+ // Verify state correctly set.
+ verify(mStateController).setOverlayActive(false);
+ verify(mStateController).setLowLightActive(false);
+ verify(mStateController).setEntryAnimationsFinished(false);
+ }
+
+ @Test
public void testDestroy() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 85c2819..596b903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
import android.content.res.Resources;
@@ -47,6 +48,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -109,6 +111,8 @@
View mStatusBarItemView;
@Mock
DreamOverlayStateController mDreamOverlayStateController;
+ @Mock
+ UserTracker mUserTracker;
@Captor
private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
@@ -125,6 +129,7 @@
.thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING);
doCallRealMethod().when(mView).setVisibility(anyInt());
doCallRealMethod().when(mView).getVisibility();
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
mController = new DreamOverlayStatusBarViewController(
mView,
@@ -140,7 +145,8 @@
mZenModeController,
mStatusBarWindowStateController,
mDreamOverlayStatusBarItemsProvider,
- mDreamOverlayStateController);
+ mDreamOverlayStateController,
+ mUserTracker);
}
@Test
@@ -282,7 +288,8 @@
mZenModeController,
mStatusBarWindowStateController,
mDreamOverlayStatusBarItemsProvider,
- mDreamOverlayStateController);
+ mDreamOverlayStateController,
+ mUserTracker);
controller.onViewAttached();
verify(mView, never()).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
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 0a03b2c..c0af0cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -173,6 +173,7 @@
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
set(Flags.REVAMPED_WALLPAPER_UI, true)
+ set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
},
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index e9db8cc..b9cfc65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.ActivityManager;
@@ -35,12 +36,12 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.UserHandle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -71,6 +72,7 @@
private @Mock Context mContext;
private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
private @Mock IActivityTaskManager mIActivityTaskManager;
+ private @Mock UserTracker mUserTracker;
private WorkLockActivityController mController;
private TaskStackChangeListener mTaskStackListener;
@@ -81,12 +83,13 @@
// Set a package name to use for checking ComponentName well-formedness in tests.
doReturn("com.example.test").when(mContext).getPackageName();
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
// Construct controller. Save the TaskStackListener for injecting events.
final ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
ArgumentCaptor.forClass(TaskStackChangeListener.class);
- mController = new WorkLockActivityController(mContext, mTaskStackChangeListeners,
- mIActivityTaskManager);
+ mController = new WorkLockActivityController(mContext, mUserTracker,
+ mTaskStackChangeListeners, mIActivityTaskManager);
verify(mTaskStackChangeListeners).registerTaskStackListener(listenerCaptor.capture());
mTaskStackListener = listenerCaptor.getValue();
@@ -135,7 +138,7 @@
anyInt(),
eq((ProfilerInfo) null),
argThat(hasOptions(taskId, taskOverlay)),
- eq(UserHandle.USER_CURRENT));
+ eq(ActivityManager.getCurrentUser()));
}
private void verifyStartActivity(int taskId, boolean taskOverlay) throws Exception {
@@ -151,7 +154,7 @@
anyInt(),
eq((ProfilerInfo) null),
argThat(hasOptions(taskId, taskOverlay)),
- eq(UserHandle.USER_CURRENT));
+ eq(ActivityManager.getCurrentUser()));
}
private static ArgumentMatcher<Intent> hasComponent(final Context context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 7205f30..8da4eae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -22,15 +22,21 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@@ -62,4 +68,24 @@
.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
}
+
+ @Test
+ fun `getPickerScreenState - default when launchable`() = runTest {
+ setLaunchable(true)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+ setLaunchable(false)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(isLaunchable: Boolean) {
+ whenever(cameraGestureHelper.canCameraGestureBeLaunched(anyInt())).thenReturn(isLaunchable)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index ca44fa18..8f56b95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -114,20 +114,8 @@
}
@Test
- fun `affordance - missing icon - model is none`() = runBlockingTest {
- setUpState(hasWalletIcon = false)
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
-
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-
- job.cancel()
- }
-
- @Test
fun `affordance - no selected card - model is none`() = runBlockingTest {
- setUpState(hasWalletIcon = false)
+ setUpState(hasSelectedCard = false)
var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +153,7 @@
@Test
fun `getPickerScreenState - unavailable`() = runTest {
setUpState(
- isWalletEnabled = false,
+ isWalletServiceAvailable = false,
)
assertThat(underTest.getPickerScreenState())
@@ -173,9 +161,9 @@
}
@Test
- fun `getPickerScreenState - disabled when there is no icon`() = runTest {
+ fun `getPickerScreenState - disabled when the feature is not enabled`() = runTest {
setUpState(
- hasWalletIcon = false,
+ isWalletEnabled = false,
)
assertThat(underTest.getPickerScreenState())
@@ -194,20 +182,16 @@
private fun setUpState(
isWalletEnabled: Boolean = true,
+ isWalletServiceAvailable: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
- hasWalletIcon: Boolean = true,
hasSelectedCard: Boolean = true,
) {
whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
val walletClient: QuickAccessWalletClient = mock()
- val icon: Drawable? =
- if (hasWalletIcon) {
- ICON
- } else {
- null
- }
- whenever(walletClient.tileIcon).thenReturn(icon)
+ whenever(walletClient.tileIcon).thenReturn(ICON)
+ whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+
whenever(walletController.walletClient).thenReturn(walletClient)
whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..805dcec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.mockito.any
+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
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+
+ private lateinit var underTest: VideoCameraQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ VideoCameraQuickAffordanceConfig(
+ context = context,
+ cameraIntents = CameraIntentsWrapper(context),
+ activityIntentHelper = activityIntentHelper,
+ userTracker = FakeUserTracker(),
+ )
+ }
+
+ @Test
+ fun `lockScreenState - visible when launchable`() = runTest {
+ setLaunchable(true)
+
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+ assertThat(lockScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+ }
+
+ @Test
+ fun `lockScreenState - hidden when not launchable`() = runTest {
+ setLaunchable(false)
+
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+ assertThat(lockScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
+
+ @Test
+ fun `getPickerScreenState - default when launchable`() = runTest {
+ setLaunchable(true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+ setLaunchable(false)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(isLaunchable: Boolean) {
+ whenever(
+ activityIntentHelper.getTargetActivityInfo(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(
+ if (isLaunchable) {
+ mock()
+ } else {
+ null
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
new file mode 100644
index 0000000..c4ae2db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.hardware.biometrics.BiometricSourceType
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+ private lateinit var testScope: TestScope
+
+ private lateinit var underTest: DeviceEntryFingerprintAuthRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+
+ underTest = DeviceEntryFingerprintAuthRepositoryImpl(keyguardUpdateMonitor)
+ }
+
+ @After
+ fun tearDown() {
+ verify(keyguardUpdateMonitor).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun isLockedOut_whenFingerprintLockoutStateChanges_emitsNewValue() =
+ testScope.runTest {
+ val isLockedOutValue = collectLastValue(underTest.isLockedOut)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(callbackCaptor.capture())
+ val callback = callbackCaptor.value
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+
+ callback.onLockedOutStateChanged(BiometricSourceType.FACE)
+ assertThat(isLockedOutValue()).isFalse()
+
+ callback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
+ assertThat(isLockedOutValue()).isTrue()
+
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+ callback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
+ assertThat(isLockedOutValue()).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
new file mode 100644
index 0000000..4b06905
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+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.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class TrustRepositoryTest : SysuiTestCase() {
+ @Mock private lateinit var trustManager: TrustManager
+ @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var testScope: TestScope
+ private val users = listOf(UserInfo(1, "user 1", 0), UserInfo(2, "user 1", 0))
+
+ private lateinit var underTest: TrustRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+ userRepository = FakeUserRepository()
+ userRepository.setUserInfos(users)
+
+ val logger =
+ TrustRepositoryLogger(
+ LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false)
+ )
+ underTest =
+ TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger)
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+
+ val currentUserId = users[0].id
+ userRepository.setSelectedUserInfo(users[0])
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+ listener.onTrustChanged(true, false, currentUserId, 0, emptyList())
+ assertThat(isCurrentUserTrusted()).isTrue()
+
+ listener.onTrustChanged(false, false, currentUserId, 0, emptyList())
+
+ assertThat(isCurrentUserTrusted()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_isFalse_byDefault() =
+ testScope.runTest {
+ runCurrent()
+
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+ assertThat(isCurrentUserTrusted()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenTrustChangesForDifferentUser_noop() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ userRepository.setSelectedUserInfo(users[0])
+ val listener = listenerCaptor.value
+
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+ // current user is trusted.
+ listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ // some other user is not trusted.
+ listener.onTrustChanged(false, false, users[1].id, 0, emptyList())
+
+ assertThat(isCurrentUserTrusted()).isTrue()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenTrustChangesForCurrentUser_emitsNewValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ userRepository.setSelectedUserInfo(users[0])
+
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+ listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ assertThat(isCurrentUserTrusted()).isTrue()
+
+ listener.onTrustChanged(false, true, users[0].id, 0, emptyList())
+ assertThat(isCurrentUserTrusted()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ userRepository.setSelectedUserInfo(users[0])
+ listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+ userRepository.setSelectedUserInfo(users[1])
+
+ assertThat(isCurrentUserTrusted()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_trustChangesFirstBeforeUserInfoChanges_emitsCorrectValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+ listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ assertThat(isCurrentUserTrusted()).isFalse()
+
+ userRepository.setSelectedUserInfo(users[0])
+
+ assertThat(isCurrentUserTrusted()).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index 9c4e849..b3e621e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -48,6 +48,7 @@
viewUtil: ViewUtil,
wakeLockBuilder: WakeLock.Builder,
systemClock: SystemClock,
+ rippleController: MediaTttReceiverRippleController,
) :
MediaTttChipControllerReceiver(
commandQueue,
@@ -65,6 +66,7 @@
viewUtil,
wakeLockBuilder,
systemClock,
+ rippleController,
) {
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index cefc742..5e40898 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -85,6 +85,8 @@
private lateinit var windowManager: WindowManager
@Mock
private lateinit var commandQueue: CommandQueue
+ @Mock
+ private lateinit var rippleController: MediaTttReceiverRippleController
private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable
private lateinit var uiEventLoggerFake: UiEventLoggerFake
@@ -134,6 +136,7 @@
viewUtil,
fakeWakeLockBuilder,
fakeClock,
+ rippleController,
)
controllerReceiver.start()
@@ -163,6 +166,7 @@
viewUtil,
fakeWakeLockBuilder,
fakeClock,
+ rippleController,
)
controllerReceiver.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index f5b3959..54d4460 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,13 +45,18 @@
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+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.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLockFake
@@ -61,6 +66,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
+import org.mockito.Mockito.atLeast
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -93,6 +99,7 @@
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var swipeHandler: SwipeChipbarAwayGestureHandler
private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
private lateinit var fakeWakeLock: WakeLockFake
private lateinit var chipbarCoordinator: ChipbarCoordinator
@@ -143,6 +150,7 @@
powerManager,
falsingManager,
falsingCollector,
+ swipeHandler,
viewUtil,
vibratorHelper,
fakeWakeLockBuilder,
@@ -161,9 +169,7 @@
)
underTest.start()
- val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
- verify(commandQueue).addCallback(callbackCaptor.capture())
- commandQueueCallback = callbackCaptor.value!!
+ setCommandQueueCallback()
}
@Test
@@ -920,6 +926,172 @@
verify(windowManager).removeView(any())
}
+ @Test
+ fun newState_viewListenerRegistered() {
+ val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+ underTest =
+ MediaTttSenderCoordinator(
+ mockChipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+ // Re-set the command queue callback since we've created a new [MediaTttSenderCoordinator]
+ // with a new callback.
+ setCommandQueueCallback()
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null,
+ )
+
+ verify(mockChipbarCoordinator).registerListener(any())
+ }
+
+ @Test
+ fun onInfoPermanentlyRemoved_viewListenerUnregistered() {
+ val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+ underTest =
+ MediaTttSenderCoordinator(
+ mockChipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+ setCommandQueueCallback()
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null,
+ )
+
+ val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
+ verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
+
+ // WHEN the listener is notified that the view has been removed
+ listenerCaptor.value.onInfoPermanentlyRemoved(DEFAULT_ID)
+
+ // THEN the media coordinator unregisters the listener
+ verify(mockChipbarCoordinator).unregisterListener(listenerCaptor.value)
+ }
+
+ @Test
+ fun onInfoPermanentlyRemoved_wrongId_viewListenerNotUnregistered() {
+ val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+ underTest =
+ MediaTttSenderCoordinator(
+ mockChipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+ setCommandQueueCallback()
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null,
+ )
+
+ val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
+ verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
+
+ // WHEN the listener is notified that a different view has been removed
+ listenerCaptor.value.onInfoPermanentlyRemoved("differentViewId")
+
+ // THEN the media coordinator doesn't unregister the listener
+ verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value)
+ }
+
+ @Test
+ fun farFromReceiverState_viewListenerUnregistered() {
+ val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+ underTest =
+ MediaTttSenderCoordinator(
+ mockChipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+ setCommandQueueCallback()
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null,
+ )
+
+ val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
+ verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
+
+ // WHEN we go to the FAR_FROM_RECEIVER state
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ // THEN the media coordinator unregisters the listener
+ verify(mockChipbarCoordinator).unregisterListener(listenerCaptor.value)
+ }
+
+ @Test
+ fun statesWithDifferentIds_onInfoPermanentlyRemovedForOneId_viewListenerNotUnregistered() {
+ val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+ underTest =
+ MediaTttSenderCoordinator(
+ mockChipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+ setCommandQueueCallback()
+
+ // WHEN there are two different media transfers with different IDs
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ MediaRoute2Info.Builder("route1", OTHER_DEVICE_NAME)
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build(),
+ null,
+ )
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ MediaRoute2Info.Builder("route2", OTHER_DEVICE_NAME)
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build(),
+ null,
+ )
+
+ val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
+ verify(mockChipbarCoordinator, atLeast(1)).registerListener(capture(listenerCaptor))
+
+ // THEN one of them is removed
+ listenerCaptor.value.onInfoPermanentlyRemoved("route1")
+
+ // THEN the media coordinator doesn't unregister the listener (since route2 is still active)
+ verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value)
+ }
+
private fun getChipbarView(): ViewGroup {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
verify(windowManager).addView(viewCaptor.capture(), any())
@@ -960,8 +1132,16 @@
null
)
}
+
+ private fun setCommandQueueCallback() {
+ val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
+ verify(commandQueue).addCallback(capture(callbackCaptor))
+ commandQueueCallback = callbackCaptor.value
+ reset(commandQueue)
+ }
}
+private const val DEFAULT_ID = "defaultId"
private const val APP_NAME = "Fake app name"
private const val OTHER_DEVICE_NAME = "My Tablet"
private const val BLANK_DEVICE_NAME = " "
@@ -969,13 +1149,13 @@
private const val TIMEOUT = 10000
private val routeInfo =
- MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
+ MediaRoute2Info.Builder(DEFAULT_ID, OTHER_DEVICE_NAME)
.addFeature("feature")
.setClientPackageName(PACKAGE_NAME)
.build()
private val routeInfoWithBlankDeviceName =
- MediaRoute2Info.Builder("id", BLANK_DEVICE_NAME)
+ MediaRoute2Info.Builder(DEFAULT_ID, BLANK_DEVICE_NAME)
.addFeature("feature")
.setClientPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index 1bc4719..1a35502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -31,10 +31,7 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -69,7 +66,7 @@
// Expressive applies hue rotations to the theme color. The input theme color has hue
// 117, ensuring the hue changed significantly is a strong signal styles are being applied.
ColorScheme colorScheme = new ColorScheme(wallpaperColors, false, Style.EXPRESSIVE);
- Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().get(6)).getHue(), 0.1);
+ Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().getS500()).getHue(), 0.1);
}
@@ -111,7 +108,8 @@
public void testTertiaryHueWrapsProperly() {
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */);
- int tertiaryMid = colorScheme.getAccent3().get(colorScheme.getAccent3().size() / 2);
+ int tertiaryMid = colorScheme.getAccent3().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Cam cam = Cam.fromInt(tertiaryMid);
Assert.assertEquals(cam.getHue(), 50.0, 10.0);
}
@@ -121,7 +119,8 @@
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
Style.SPRITZ /* style */);
- int primaryMid = colorScheme.getAccent1().get(colorScheme.getAccent1().size() / 2);
+ int primaryMid = colorScheme.getAccent1().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Cam cam = Cam.fromInt(primaryMid);
Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
}
@@ -131,7 +130,8 @@
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
Style.VIBRANT /* style */);
- int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+ int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Cam cam = Cam.fromInt(neutralMid);
Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 12.0);
}
@@ -141,7 +141,8 @@
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
Style.EXPRESSIVE /* style */);
- int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+ int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Cam cam = Cam.fromInt(neutralMid);
Assert.assertTrue(cam.getChroma() <= 8.0);
}
@@ -151,10 +152,11 @@
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
Style.MONOCHROMATIC /* style */);
- int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+ int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Assert.assertTrue(
Color.red(neutralMid) == Color.green(neutralMid)
- && Color.green(neutralMid) == Color.blue(neutralMid)
+ && Color.green(neutralMid) == Color.blue(neutralMid)
);
}
@@ -190,15 +192,14 @@
xml.append(" <").append(styleName).append(">");
List<String> colors = new ArrayList<>();
- for (Stream<Integer> stream: Arrays.asList(colorScheme.getAccent1().stream(),
- colorScheme.getAccent2().stream(),
- colorScheme.getAccent3().stream(),
- colorScheme.getNeutral1().stream(),
- colorScheme.getNeutral2().stream())) {
+
+ colorScheme.getAllHues().forEach(schemeHue -> {
colors.add("ffffff");
- colors.addAll(stream.map(Integer::toHexString).map(s -> s.substring(2)).collect(
- Collectors.toList()));
- }
+ schemeHue.getAllShades().forEach(tone -> {
+ colors.add(Integer.toHexString(tone).substring(2));
+ });
+ });
+
xml.append(String.join(",", colors));
xml.append("</").append(styleName).append(">\n");
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 8b0342e..8058b85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -55,8 +55,8 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -109,7 +109,8 @@
TaskStackChangeListeners.getTestInstance(),
Optional.of(mock(Pip.class)),
Optional.of(mock(BackAnimation.class)),
- mock(FeatureFlags.class)));
+ mock(FeatureFlags.class),
+ mock(SecureSettings.class)));
initializeNavigationBars();
mMockitoSession = mockitoSession().mockStatic(Utilities.class).startMocking();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..a1d42a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.notetask.quickaffordance
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskQuickAffordanceConfig].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskQuickAffordanceConfigTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock lateinit var noteTaskController: NoteTaskController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(noteTaskController.showNoteTask()).then {}
+ }
+
+ private fun createUnderTest(isEnabled: Boolean) =
+ NoteTaskQuickAffordanceConfig(
+ context = context,
+ noteTaskController = noteTaskController,
+ isEnabled = isEnabled,
+ )
+
+ @Test
+ fun lockScreenState_isNotEnabled_shouldEmitHidden() = runTest {
+ val underTest = createUnderTest(isEnabled = false)
+
+ val actual = collectLastValue(underTest.lockScreenState)
+
+ assertThat(actual()).isEqualTo(LockScreenState.Hidden)
+ }
+
+ @Test
+ fun lockScreenState_isEnabled_shouldEmitVisible() = runTest {
+ val stringResult = "Notetaking"
+ val underTest = createUnderTest(isEnabled = true)
+
+ val actual = collectLastValue(underTest.lockScreenState)
+
+ val expected =
+ LockScreenState.Visible(
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_note_task_shortcut_keyguard,
+ contentDescription = ContentDescription.Loaded(stringResult),
+ )
+ )
+ assertThat(actual()).isEqualTo(expected)
+ }
+
+ @Test
+ fun onTriggered_shouldLaunchNoteTask() {
+ val underTest = createUnderTest(isEnabled = false)
+
+ underTest.onTriggered(expandable = null)
+
+ verify(noteTaskController).showNoteTask()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 3528e14..3da7a22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -30,6 +30,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -54,6 +55,7 @@
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.settings.FakeSettings;
@@ -86,6 +88,8 @@
@Mock
private UiEventLogger mUiEventLogger;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private View mView;
private BroadcastReceiver mReceiver;
@@ -107,9 +111,12 @@
mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
ActivityStarter starter = mDependency.injectMockDependency(ActivityStarter.class);
BroadcastSender broadcastSender = mDependency.injectMockDependency(BroadcastSender.class);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ when(mUserTracker.getUserHandle()).thenReturn(
+ UserHandle.of(ActivityManager.getCurrentUser()));
mPowerNotificationWarnings = new PowerNotificationWarnings(wrapper, starter,
broadcastSender, () -> mBatteryController, mDialogLaunchAnimator, mUiEventLogger,
- mGlobalSettings);
+ mGlobalSettings, mUserTracker);
BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
mPowerNotificationWarnings.updateSnapshot(snapshot);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 46a502a..ed3f1a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -22,15 +22,12 @@
import android.graphics.Insets
import android.graphics.Rect
import android.hardware.HardwareBuffer
-import android.os.Bundle
import android.os.UserHandle
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
@@ -49,7 +46,6 @@
private val bounds = Rect(25, 25, 75, 75)
private val scope = CoroutineScope(Dispatchers.Unconfined)
- private val dispatcher = Dispatchers.Unconfined
private val policy = FakeScreenshotPolicy()
private val flags = FakeFeatureFlags()
@@ -58,7 +54,8 @@
fun testProcessAsync() {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
var result: ScreenshotRequest? = null
@@ -80,7 +77,8 @@
fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
@@ -97,9 +95,11 @@
policy.setManagedProfile(USER_ID, false)
policy.setDisplayContentInfo(
policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+ )
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
@@ -120,17 +120,20 @@
// Indicate that the primary content belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
- policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
+ policy.setDisplayContentInfo(
+ policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+ )
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
// Expect a task snapshot is taken, overriding the full screen mode
assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
- assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+ assertThat(bitmap.equalsHardwareBitmap(processedRequest.bitmap)).isTrue()
assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
@@ -147,10 +150,16 @@
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val bitmap = makeHardwareBitmap(100, 100)
- val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
- bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(component)
+ .setTaskId(TASK_ID)
+ .setUserId(USER_ID)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(bounds)
+ .setInsets(Insets.NONE)
+ .build()
val processedRequest = processor.process(request)
@@ -168,10 +177,16 @@
policy.setManagedProfile(USER_ID, false)
val bitmap = makeHardwareBitmap(100, 100)
- val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
- bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(component)
+ .setTaskId(TASK_ID)
+ .setUserId(USER_ID)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(bounds)
+ .setInsets(Insets.NONE)
+ .build()
val processedRequest = processor.process(request)
@@ -190,10 +205,16 @@
policy.setManagedProfile(USER_ID, true)
val bitmap = makeHardwareBitmap(100, 100)
- val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
- bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(component)
+ .setTaskId(TASK_ID)
+ .setUserId(USER_ID)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(bounds)
+ .setInsets(Insets.NONE)
+ .build()
val processedRequest = processor.process(request)
@@ -202,14 +223,18 @@
}
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
- val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
- HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+ val buffer =
+ HardwareBuffer.create(
+ width,
+ height,
+ HardwareBuffer.RGBA_8888,
+ 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ )
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
}
- private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
- val provided = bundleToHardwareBitmap(bundle)
- return provided.hardwareBuffer == this.hardwareBuffer &&
- provided.colorSpace == this.colorSpace
+ private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
+ return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 99c79b0..f935019 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -35,8 +35,7 @@
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.ScreenshotHelper
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
@@ -80,24 +79,39 @@
private val flags = FakeFeatureFlags()
private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
- private val service = TakeScreenshotService(
- controller, userManager, devicePolicyManager, eventLogger,
- notificationsController, mContext, Runnable::run, flags, requestProcessor)
+ private val service =
+ TakeScreenshotService(
+ controller,
+ userManager,
+ devicePolicyManager,
+ eventLogger,
+ notificationsController,
+ mContext,
+ Runnable::run,
+ flags,
+ requestProcessor
+ )
@Before
fun setUp() {
whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
- whenever(devicePolicyManager.getScreenCaptureDisabled(
- /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL)))
+ whenever(
+ devicePolicyManager.getScreenCaptureDisabled(
+ /* admin component (null: any admin) */ isNull(),
+ eq(UserHandle.USER_ALL)
+ )
+ )
.thenReturn(false)
whenever(userManager.isUserUnlocked).thenReturn(true)
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
- val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
- val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
- consumer.accept(request)
- }.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any())
+ val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
+ val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
+ consumer.accept(request)
+ }
+ .`when`(requestProcessor)
+ .processAsync(/* request= */ any(), /* callback= */ any())
// Flipped in selected test cases
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
@@ -108,7 +122,8 @@
/* className = */ null,
/* token = */ null,
application,
- /* activityManager = */ null)
+ /* activityManager = */ null
+ )
}
@Test
@@ -125,63 +140,89 @@
@Test
fun takeScreenshotFullscreen() {
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_CHORD,
- topComponent)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ .setTopComponent(topComponent)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
- verify(controller, times(1)).takeScreenshotFullscreen(
- eq(topComponent),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any())
+ verify(controller, times(1))
+ .takeScreenshotFullscreen(
+ eq(topComponent),
+ /* onSavedListener = */ any(),
+ /* requestCallback = */ any()
+ )
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
val logEvent = eventLogger.get(0)
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED UiEvent",
+ logEvent.eventId,
+ SCREENSHOT_REQUESTED_KEY_CHORD.id
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ eventLogger.get(0).packageName
+ )
}
@Test
fun takeScreenshotProvidedImage() {
val bounds = Rect(50, 50, 150, 150)
val bitmap = makeHardwareBitmap(100, 100)
- val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW,
- bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW)
+ .setTopComponent(topComponent)
+ .setTaskId(TASK_ID)
+ .setUserId(USER_ID)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(bounds)
+ .setInsets(Insets.NONE)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
- verify(controller, times(1)).handleImageAsScreenshot(
- argThat { b -> b.equalsHardwareBitmap(bitmap) },
- eq(bounds),
- eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent),
- /* onSavedListener = */ any(), /* requestCallback = */ any())
+ verify(controller, times(1))
+ .handleImageAsScreenshot(
+ argThat { b -> b.equalsHardwareBitmap(bitmap) },
+ eq(bounds),
+ eq(Insets.NONE),
+ eq(TASK_ID),
+ eq(USER_ID),
+ eq(topComponent),
+ /* onSavedListener = */ any(),
+ /* requestCallback = */ any()
+ )
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
val logEvent = eventLogger.get(0)
- assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ logEvent.eventId,
+ SCREENSHOT_REQUESTED_OVERVIEW.id
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ eventLogger.get(0).packageName
+ )
}
@Test
fun takeScreenshotFullscreen_userLocked() {
whenever(userManager.isUserUnlocked).thenReturn(false)
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_CHORD,
- topComponent)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ .setTopComponent(topComponent)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
verify(callback, times(1)).reportError()
@@ -190,21 +231,24 @@
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
- whenever(devicePolicyManager.getScreenCaptureDisabled(
- isNull(), eq(UserHandle.USER_ALL))
- ).thenReturn(true)
+ whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
+ .thenReturn(true)
- whenever(devicePolicyResourcesManager.getString(
- eq(SCREENSHOT_BLOCKED_BY_ADMIN),
- /* Supplier<String> */ any(),
- )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+ whenever(
+ devicePolicyResourcesManager.getString(
+ eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+ /* Supplier<String> */
+ any(),
+ )
+ )
+ .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_CHORD,
- topComponent)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ .setTopComponent(topComponent)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
// error shown: Toast.makeText(...).show(), untestable
verify(callback, times(1)).reportError()
@@ -214,14 +258,20 @@
private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
return config == HARDWARE &&
- other.config == HARDWARE &&
- hardwareBuffer == other.hardwareBuffer &&
- colorSpace == other.colorSpace
+ other.config == HARDWARE &&
+ hardwareBuffer == other.hardwareBuffer &&
+ colorSpace == other.colorSpace
}
/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
- val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
- HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+ val buffer =
+ HardwareBuffer.create(
+ width,
+ height,
+ HardwareBuffer.RGBA_8888,
+ 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ )
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
index 3710281..57b6b2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.settings
+import android.app.IActivityManager
import android.content.Context
import android.content.Intent
import android.content.pm.UserInfo
@@ -51,6 +52,7 @@
@Mock private lateinit var context: Context
@Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var iActivityManager: IActivityManager
@Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
@Mock(stubOnly = true) private lateinit var handler: Handler
@@ -67,7 +69,7 @@
`when`(context.user).thenReturn(UserHandle.SYSTEM)
`when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context)
- tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+ tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index e65bbb1..71ba215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -16,11 +16,14 @@
package com.android.systemui.settings
+import android.app.IActivityManager
+import android.app.IUserSwitchObserver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.UserInfo
import android.os.Handler
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
@@ -29,19 +32,20 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -51,6 +55,10 @@
private lateinit var context: Context
@Mock
private lateinit var userManager: UserManager
+ @Mock
+ private lateinit var iActivityManager: IActivityManager
+ @Mock
+ private lateinit var userSwitchingReply: IRemoteCallback
@Mock(stubOnly = true)
private lateinit var dumpManager: DumpManager
@Mock(stubOnly = true)
@@ -76,7 +84,7 @@
listOf(info)
}
- tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+ tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler)
}
@Test
@@ -125,8 +133,7 @@
verify(context).registerReceiverForAllUsers(
eq(tracker), capture(captor), isNull(), eq(handler))
with(captor.value) {
- assertThat(countActions()).isEqualTo(7)
- assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue()
+ assertThat(countActions()).isEqualTo(6)
assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
@@ -158,8 +165,10 @@
tracker.initialize(0)
val newID = 5
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID)
- tracker.onReceive(context, intent)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ verify(userSwitchingReply).sendResult(any())
verify(userManager).getProfiles(newID)
@@ -272,6 +281,24 @@
}
@Test
+ fun testCallbackCalledOnUserChanging() {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
+
+ val newID = 5
+
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ verify(userSwitchingReply).sendResult(any())
+
+ assertThat(callback.calledOnUserChanging).isEqualTo(1)
+ assertThat(callback.lastUser).isEqualTo(newID)
+ assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+ }
+
+ @Test
fun testCallbackCalledOnUserChanged() {
tracker.initialize(0)
val callback = TestCallback()
@@ -279,8 +306,9 @@
val newID = 5
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID)
- tracker.onReceive(context, intent)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitchComplete(newID)
assertThat(callback.calledOnUserChanged).isEqualTo(1)
assertThat(callback.lastUser).isEqualTo(newID)
@@ -330,25 +358,36 @@
tracker.addCallback(callback, executor)
tracker.removeCallback(callback)
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, 5)
- tracker.onReceive(context, intent)
+ val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+ verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onUserSwitching(newID, userSwitchingReply)
+ verify(userSwitchingReply).sendResult(any())
+ captor.value.onUserSwitchComplete(newID)
val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
.putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
tracker.onReceive(context, intentProfiles)
+ assertThat(callback.calledOnUserChanging).isEqualTo(0)
assertThat(callback.calledOnUserChanged).isEqualTo(0)
assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
}
private class TestCallback : UserTracker.Callback {
+ var calledOnUserChanging = 0
var calledOnUserChanged = 0
var calledOnProfilesChanged = 0
var lastUser: Int? = null
var lastUserContext: Context? = null
var lastUserProfiles = emptyList<UserInfo>()
+ override fun onUserChanging(newUser: Int, userContext: Context) {
+ calledOnUserChanging++
+ lastUser = newUser
+ lastUserContext = userContext
+ }
+
override fun onUserChanged(newUser: Int, userContext: Context) {
calledOnUserChanged++
lastUser = newUser
@@ -360,4 +399,4 @@
lastUserProfiles = profiles
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 3e769e9..76aa08a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.tuner.TunerService
import com.android.systemui.tuner.TunerService.Tunable
@@ -69,6 +70,8 @@
private lateinit var statusBarStateController: StatusBarStateController
@Mock
private lateinit var shadeLogger: ShadeLogger
+ @Mock
+ private lateinit var userTracker: UserTracker
private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
private lateinit var underTest: PulsingGestureListener
@@ -85,6 +88,7 @@
ambientDisplayConfiguration,
statusBarStateController,
shadeLogger,
+ userTracker,
tunerService,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index fc7cd89..0000c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -209,9 +209,9 @@
@Test
public void testShowRecentApps() {
- mCommandQueue.showRecentApps(true, false);
+ mCommandQueue.showRecentApps(true);
waitForIdleSync();
- verify(mCallbacks).showRecentApps(eq(true), eq(false));
+ verify(mCallbacks).showRecentApps(eq(true));
}
@Test
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 58fe2a0..831d07f 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
@@ -41,6 +41,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
@@ -59,6 +60,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
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.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -107,6 +109,8 @@
UiEventLoggerFake mUiEventLoggerFake;
@Mock
PendingIntent mPendingIntent;
+ @Mock
+ UserTracker mUserTracker;
private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
@@ -114,6 +118,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
mUiEventLoggerFake = new UiEventLoggerFake();
@@ -131,7 +136,8 @@
mMockHandler,
mFlags,
mKeyguardNotificationVisibilityProvider,
- mUiEventLoggerFake);
+ mUiEventLoggerFake,
+ mUserTracker);
mNotifInterruptionStateProvider.mUseHeadsUp = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
index 1f92b0a..819a75b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
@@ -98,5 +100,16 @@
mView.setSecondaryVisible(true /* visible */, true /* animate */);
}
+
+ @Test
+ public void testSetFooterLabelTextAndIcon() {
+ mView.setFooterLabelTextAndIcon(
+ R.string.unlock_to_see_notif_text,
+ R.drawable.ic_friction_lock_closed);
+ assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
+ assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE);
+ assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
+ .isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 645052f..9f6f082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -21,6 +21,7 @@
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -65,6 +66,7 @@
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -78,6 +80,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -135,10 +138,14 @@
@Mock private ShadeTransitionController mShadeTransitionController;
@Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationTargetsHelper mNotificationTargetsHelper;
+ @Mock private SecureSettings mSecureSettings;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
+ private final SeenNotificationsProviderImpl mSeenNotificationsProvider =
+ new SeenNotificationsProviderImpl();
+
private NotificationStackScrollLayoutController mController;
@Before
@@ -180,14 +187,15 @@
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
- new SeenNotificationsProviderImpl(),
+ mSeenNotificationsProvider,
mShadeController,
mJankMonitor,
mStackLogger,
mLogger,
mNotificationStackSizeCalculator,
mFeatureFlags,
- mNotificationTargetsHelper
+ mNotificationTargetsHelper,
+ mSecureSettings
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
@@ -233,16 +241,14 @@
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ true,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ true);
setupShowEmptyShadeViewState(false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ false,
- /* notifVisibleInShade= */ true,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ true);
}
@Test
@@ -255,16 +261,14 @@
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ false);
setupShowEmptyShadeViewState(false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ false,
- /* notifVisibleInShade= */ false,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ false);
}
@Test
@@ -283,16 +287,14 @@
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ false);
mController.setQsFullScreen(true);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ false);
}
@Test
@@ -400,6 +402,17 @@
verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true);
}
+ @Test
+ public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
+ when(mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()).thenReturn(true);
+ mSeenNotificationsProvider.setHasFilteredOutSeenNotifications(true);
+ mController.attach(mNotificationStackScrollLayout);
+ mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
+ verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
+ verify(mNotificationStackScrollLayout).updateFooter();
+ verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean());
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
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 7622549..dd7143a 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
@@ -30,6 +30,7 @@
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -53,6 +54,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -328,7 +330,7 @@
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, true, false);
+ mStackScroller.updateEmptyShadeView(true, true);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -338,7 +340,7 @@
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false, false);
+ mStackScroller.updateEmptyShadeView(true, false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
}
@@ -347,10 +349,10 @@
public void updateEmptyView_noNotificationsToDndSuppressing() {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false, false);
+ mStackScroller.updateEmptyShadeView(true, false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
- mStackScroller.updateEmptyShadeView(true, true, false);
+ mStackScroller.updateEmptyShadeView(true, true);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -818,6 +820,29 @@
assertEquals(0f, mAmbientState.getStackY());
}
+ @Test
+ public void hasFilteredOutSeenNotifs_updateFooter() {
+ mStackScroller.setCurrentUserSetup(true);
+
+ // add footer
+ mStackScroller.inflateFooterView();
+ TextView footerLabel =
+ mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer);
+
+ mStackScroller.setHasFilteredOutSeenNotifications(true);
+ mStackScroller.updateFooter();
+
+ assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
+ mStackScroller.setHasFilteredOutSeenNotifications(true);
+ mStackScroller.updateEmptyShadeView(true, false);
+
+ verify(mEmptyShadeView).setFooterText(not(0));
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index c17c5b0..52bd424 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -25,8 +25,10 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.os.PowerManager;
+import android.os.UserHandle;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.view.WindowInsets;
@@ -41,6 +43,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
@@ -88,6 +91,7 @@
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock private SystemBarAttributesListener mSystemBarAttributesListener;
@Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
+ @Mock private UserTracker mUserTracker;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -120,8 +124,11 @@
new DisableFlagsLogger(),
DEFAULT_DISPLAY,
mSystemBarAttributesListener,
- mCameraLauncherLazy);
+ mCameraLauncherLazy,
+ mUserTracker);
+ when(mUserTracker.getUserHandle()).thenReturn(
+ UserHandle.of(ActivityManager.getCurrentUser()));
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
.thenAnswer((Answer<Integer>) invocation -> invocation.getArgument(0));
@@ -140,7 +147,7 @@
// Trying to open it does nothing.
mSbcqCallbacks.animateExpandNotificationsPanel();
- verify(mNotificationPanelViewController, never()).expandWithoutQs();
+ verify(mNotificationPanelViewController, never()).expandShadeToNotifications();
mSbcqCallbacks.animateExpandSettingsPanel(null);
verify(mNotificationPanelViewController, never()).expand(anyBoolean());
}
@@ -158,7 +165,7 @@
// Can now be opened.
mSbcqCallbacks.animateExpandNotificationsPanel();
- verify(mNotificationPanelViewController).expandWithoutQs();
+ verify(mNotificationPanelViewController).expandShadeToNotifications();
mSbcqCallbacks.animateExpandSettingsPanel(null);
verify(mNotificationPanelViewController).expandWithQs();
}
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 4c1b219..b1363a0 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
@@ -44,6 +44,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.IWallpaperManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -118,6 +119,7 @@
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
@@ -306,6 +308,7 @@
*/
@Mock private ViewRootImpl mViewRootImpl;
@Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ @Mock private UserTracker mUserTracker;
@Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
@Mock IPowerManager mPowerManagerService;
@@ -321,6 +324,10 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
+ // CentralSurfacesImpl's runtime flag check fails if the flag is absent.
+ // This value is unused, because test manifest is opted in.
+ mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
+
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
Handler.createAsync(Looper.myLooper()));
@@ -338,7 +345,8 @@
new Handler(TestableLooper.get(this).getLooper()),
mock(NotifPipelineFlags.class),
mock(KeyguardNotificationVisibilityProvider.class),
- mock(UiEventLogger.class));
+ mock(UiEventLogger.class),
+ mUserTracker);
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -419,6 +427,9 @@
when(mOperatorNameViewControllerFactory.create(any()))
.thenReturn(mOperatorNameViewController);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ when(mUserTracker.getUserHandle()).thenReturn(
+ UserHandle.of(ActivityManager.getCurrentUser()));
mCentralSurfaces = new CentralSurfacesImpl(
mContext,
@@ -508,7 +519,8 @@
mDreamManager,
mCameraLauncherLazy,
() -> mLightRevealScrimViewModel,
- mAlternateBouncerInteractor
+ mAlternateBouncerInteractor,
+ mUserTracker
) {
@Override
protected ViewRootImpl getViewRootImpl() {
@@ -1280,7 +1292,8 @@
Handler mainHandler,
NotifPipelineFlags flags,
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker) {
super(
contentResolver,
powerManager,
@@ -1294,7 +1307,8 @@
mainHandler,
flags,
keyguardNotificationVisibilityProvider,
- uiEventLogger
+ uiEventLogger,
+ userTracker
);
mUseHeadsUp = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index c843850..eb5edbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.content.res.Resources;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
@@ -44,6 +45,7 @@
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -82,6 +84,7 @@
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
+ @Mock private UserTracker mUserTracker;
@Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
/**
@@ -107,6 +110,7 @@
when(mSysUIUnfoldComponent.getFoldAodAnimationController())
.thenReturn(mFoldAodAnimationController);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
mDozeParameters = new DozeParameters(
mContext,
@@ -123,7 +127,8 @@
mUnlockedScreenOffAnimationController,
mKeyguardUpdateMonitor,
mConfigurationController,
- mStatusBarStateController
+ mStatusBarStateController,
+ mUserTracker
);
verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 64dee95..305b9fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.screenrecord.RecordingController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.statusbar.policy.CastController
@@ -71,61 +72,37 @@
private const val ALARM_SLOT = "alarm"
}
- @Mock
- private lateinit var iconController: StatusBarIconController
- @Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock
- private lateinit var castController: CastController
- @Mock
- private lateinit var hotspotController: HotspotController
- @Mock
- private lateinit var bluetoothController: BluetoothController
- @Mock
- private lateinit var nextAlarmController: NextAlarmController
- @Mock
- private lateinit var userInfoController: UserInfoController
- @Mock
- private lateinit var rotationLockController: RotationLockController
- @Mock
- private lateinit var dataSaverController: DataSaverController
- @Mock
- private lateinit var zenModeController: ZenModeController
- @Mock
- private lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var locationController: LocationController
- @Mock
- private lateinit var sensorPrivacyController: SensorPrivacyController
- @Mock
- private lateinit var iActivityManager: IActivityManager
- @Mock
- private lateinit var alarmManager: AlarmManager
- @Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock
- private lateinit var recordingController: RecordingController
- @Mock
- private lateinit var telecomManager: TelecomManager
- @Mock
- private lateinit var sharedPreferences: SharedPreferences
- @Mock
- private lateinit var dateFormatUtil: DateFormatUtil
+ @Mock private lateinit var iconController: StatusBarIconController
+ @Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var castController: CastController
+ @Mock private lateinit var hotspotController: HotspotController
+ @Mock private lateinit var bluetoothController: BluetoothController
+ @Mock private lateinit var nextAlarmController: NextAlarmController
+ @Mock private lateinit var userInfoController: UserInfoController
+ @Mock private lateinit var rotationLockController: RotationLockController
+ @Mock private lateinit var dataSaverController: DataSaverController
+ @Mock private lateinit var zenModeController: ZenModeController
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var locationController: LocationController
+ @Mock private lateinit var sensorPrivacyController: SensorPrivacyController
+ @Mock private lateinit var iActivityManager: IActivityManager
+ @Mock private lateinit var alarmManager: AlarmManager
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var recordingController: RecordingController
+ @Mock private lateinit var telecomManager: TelecomManager
+ @Mock private lateinit var sharedPreferences: SharedPreferences
+ @Mock private lateinit var dateFormatUtil: DateFormatUtil
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var ringerModeTracker: RingerModeTracker
- @Mock
- private lateinit var privacyItemController: PrivacyItemController
- @Mock
- private lateinit var privacyLogger: PrivacyLogger
+ @Mock private lateinit var privacyItemController: PrivacyItemController
+ @Mock private lateinit var privacyLogger: PrivacyLogger
@Captor
private lateinit var alarmCallbackCaptor:
- ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
+ ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
private lateinit var executor: FakeExecutor
private lateinit var statusBarPolicy: PhoneStatusBarPolicy
@@ -137,8 +114,8 @@
executor = FakeExecutor(FakeSystemClock())
testableLooper = TestableLooper.get(this)
context.orCreateTestableResources.addOverride(
- com.android.internal.R.string.status_bar_alarm_clock,
- ALARM_SLOT
+ com.android.internal.R.string.status_bar_alarm_clock,
+ ALARM_SLOT
)
statusBarPolicy = createStatusBarPolicy()
}
@@ -195,36 +172,37 @@
private fun createStatusBarPolicy(): PhoneStatusBarPolicy {
return PhoneStatusBarPolicy(
- iconController,
- commandQueue,
- broadcastDispatcher,
- executor,
- testableLooper.looper,
- context.resources,
- castController,
- hotspotController,
- bluetoothController,
- nextAlarmController,
- userInfoController,
- rotationLockController,
- dataSaverController,
- zenModeController,
- deviceProvisionedController,
- keyguardStateController,
- locationController,
- sensorPrivacyController,
- iActivityManager,
- alarmManager,
- userManager,
- devicePolicyManager,
- recordingController,
- telecomManager,
- /* displayId = */ 0,
- sharedPreferences,
- dateFormatUtil,
- ringerModeTracker,
- privacyItemController,
- privacyLogger
+ iconController,
+ commandQueue,
+ broadcastDispatcher,
+ executor,
+ testableLooper.looper,
+ context.resources,
+ castController,
+ hotspotController,
+ bluetoothController,
+ nextAlarmController,
+ userInfoController,
+ rotationLockController,
+ dataSaverController,
+ zenModeController,
+ deviceProvisionedController,
+ keyguardStateController,
+ locationController,
+ sensorPrivacyController,
+ iActivityManager,
+ alarmManager,
+ userManager,
+ userTracker,
+ devicePolicyManager,
+ recordingController,
+ telecomManager,
+ /* displayId = */ 0,
+ sharedPreferences,
+ dateFormatUtil,
+ ringerModeTracker,
+ privacyItemController,
+ privacyLogger
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index c7a0582..c0537a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -630,6 +630,7 @@
@Test
public void transitionToUnlocked() {
+ mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
@@ -645,14 +646,21 @@
));
// Back scrim should be visible after start dragging
- mScrimController.setRawPanelExpansionFraction(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.29f);
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, SEMI_TRANSPARENT));
+ // Back scrim should be opaque at 30%
+ mScrimController.setRawPanelExpansionFraction(0.3f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, OPAQUE));
+
// Then, notification scrim should fade in
- mScrimController.setRawPanelExpansionFraction(0.7f);
+ mScrimController.setRawPanelExpansionFraction(0.31f);
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, SEMI_TRANSPARENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 19658e6..ccc57ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -33,6 +33,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -59,6 +60,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeControllerImpl;
@@ -139,6 +141,8 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock
private InteractionJankMonitor mJankMonitor;
+ @Mock
+ private UserTracker mUserTracker;
private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private ExpandableNotificationRow mNotificationRow;
private ExpandableNotificationRow mBubbleNotificationRow;
@@ -183,6 +187,8 @@
when(mVisibilityProvider.obtain(any(NotificationEntry.class), anyBoolean()))
.thenAnswer(invocation -> NotificationVisibility.obtain(
invocation.<NotificationEntry>getArgument(0).getKey(), 0, 1, false));
+ when(mUserTracker.getUserHandle()).thenReturn(
+ UserHandle.of(ActivityManager.getCurrentUser()));
HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
@@ -222,7 +228,8 @@
mActivityLaunchAnimator,
notificationAnimationProvider,
mock(LaunchFullScreenIntentProvider.class),
- mock(FeatureFlags.class)
+ mock(FeatureFlags.class),
+ mUserTracker
);
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index d30222f..711e4ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -50,7 +50,9 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.*
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.nullable
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
@@ -83,7 +85,8 @@
private lateinit var notifCollectionListener: NotifCollectionListener
@Mock private lateinit var mockOngoingCallFlags: OngoingCallFlags
- @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
+ @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler:
+ SwipeStatusBarAwayGestureHandler
@Mock private lateinit var mockOngoingCallListener: OngoingCallListener
@Mock private lateinit var mockActivityStarter: ActivityStarter
@Mock private lateinit var mockIActivityManager: IActivityManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 99e2012..45f7df3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -159,7 +159,7 @@
underTest.displayView(getState())
assertThat(fakeWakeLock.isHeld).isTrue()
- underTest.removeView("id", "test reason")
+ underTest.removeView(DEFAULT_ID, "test reason")
assertThat(fakeWakeLock.isHeld).isFalse()
}
@@ -175,6 +175,8 @@
@Test
fun displayView_twiceWithDifferentIds_oldViewRemovedNewViewAdded() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "name",
@@ -199,10 +201,15 @@
assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
verify(windowManager).removeView(viewCaptor.allValues[0])
+ // Since the controller is still storing the older view in case it'll get re-displayed
+ // later, the listener shouldn't be notified
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
}
@Test
fun displayView_viewDoesNotDisappearsBeforeTimeout() {
+ val listener = registerListener()
+
val state = getState()
underTest.displayView(state)
reset(windowManager)
@@ -210,10 +217,13 @@
fakeClock.advanceTime(TIMEOUT_MS - 1)
verify(windowManager, never()).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
}
@Test
fun displayView_viewDisappearsAfterTimeout() {
+ val listener = registerListener()
+
val state = getState()
underTest.displayView(state)
reset(windowManager)
@@ -221,10 +231,13 @@
fakeClock.advanceTime(TIMEOUT_MS + 1)
verify(windowManager).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
}
@Test
fun displayView_calledAgainBeforeTimeout_timeoutReset() {
+ val listener = registerListener()
+
// First, display the view
val state = getState()
underTest.displayView(state)
@@ -239,10 +252,13 @@
// Verify we didn't hide the view
verify(windowManager, never()).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
}
@Test
fun displayView_calledAgainBeforeTimeout_eventuallyTimesOut() {
+ val listener = registerListener()
+
// First, display the view
val state = getState()
underTest.displayView(state)
@@ -255,6 +271,7 @@
fakeClock.advanceTime(TIMEOUT_MS + 1)
verify(windowManager).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
}
@Test
@@ -271,25 +288,9 @@
}
@Test
- fun viewUpdatedWithNewOnViewTimeoutRunnable_newRunnableUsed() {
- var runnable1Run = false
- underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
- runnable1Run = true
- }
-
- var runnable2Run = false
- underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
- runnable2Run = true
- }
-
- fakeClock.advanceTime(TIMEOUT_MS + 1)
-
- assertThat(runnable1Run).isFalse()
- assertThat(runnable2Run).isTrue()
- }
-
- @Test
fun multipleViewsWithDifferentIds_moreRecentReplacesOlder() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "name",
@@ -315,10 +316,16 @@
assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
verify(windowManager).removeView(viewCaptor.allValues[0])
verify(configurationController, never()).removeCallback(any())
+
+ // Since the controller is still storing the older view in case it'll get re-displayed
+ // later, the listener shouldn't be notified
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
}
@Test
- fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
+ fun multipleViewsWithDifferentIds_newViewRemoved_previousViewIsDisplayed() {
+ val listener = registerListener()
+
underTest.displayView(ViewInfo("First name", id = "id1"))
verify(windowManager).addView(any(), any())
@@ -329,24 +336,35 @@
verify(windowManager).removeView(any())
verify(windowManager).addView(any(), any())
reset(windowManager)
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
+ // WHEN the current view is removed
underTest.removeView("id2", "test reason")
+ // THEN it's correctly removed
verify(windowManager).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).containsExactly("id2")
+
+ // And the previous view is correctly added
verify(windowManager).addView(any(), any())
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+ // WHEN the previous view times out
reset(windowManager)
fakeClock.advanceTime(TIMEOUT_MS + 1)
+ // THEN it is also removed
verify(windowManager).removeView(any())
assertThat(underTest.activeViews.size).isEqualTo(0)
verify(configurationController).removeCallback(any())
+ assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id2", "id1"))
}
@Test
fun multipleViewsWithDifferentIds_oldViewRemoved_recentViewIsDisplayed() {
+ val listener = registerListener()
+
underTest.displayView(ViewInfo("First name", id = "id1"))
verify(windowManager).addView(any(), any())
@@ -361,7 +379,8 @@
// WHEN an old view is removed
underTest.removeView("id1", "test reason")
- // THEN we don't update anything
+ // THEN we don't update anything except the listener
+ assertThat(listener.permanentlyRemovedIds).containsExactly("id1")
verify(windowManager, never()).removeView(any())
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
@@ -372,10 +391,13 @@
verify(windowManager).removeView(any())
assertThat(underTest.activeViews.size).isEqualTo(0)
verify(configurationController).removeCallback(any())
+ assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id1", "id2"))
}
@Test
fun multipleViewsWithDifferentIds_threeDifferentViews_recentActiveViewIsDisplayed() {
+ val listener = registerListener()
+
underTest.displayView(ViewInfo("First name", id = "id1"))
underTest.displayView(ViewInfo("Second name", id = "id2"))
underTest.displayView(ViewInfo("Third name", id = "id3"))
@@ -387,6 +409,7 @@
underTest.removeView("id3", "test reason")
verify(windowManager).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3"))
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
verify(configurationController, never()).removeCallback(any())
@@ -395,6 +418,7 @@
underTest.removeView("id2", "test reason")
verify(windowManager).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3", "id2"))
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
verify(configurationController, never()).removeCallback(any())
@@ -403,6 +427,7 @@
fakeClock.advanceTime(TIMEOUT_MS + 1)
verify(windowManager).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3", "id2", "id1"))
assertThat(underTest.activeViews.size).isEqualTo(0)
verify(configurationController).removeCallback(any())
}
@@ -438,6 +463,8 @@
@Test
fun multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayed() {
+ val listener = registerListener()
+
underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
fakeClock.advanceTime(1000)
underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 4000))
@@ -451,10 +478,13 @@
verify(windowManager, never()).addView(any(), any())
assertThat(underTest.activeViews.size).isEqualTo(0)
verify(configurationController).removeCallback(any())
+ assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2", "id3")
}
@Test
fun multipleViews_mostRecentViewRemoved_viewWithShortTimeLeftNotDisplayed() {
+ val listener = registerListener()
+
underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
fakeClock.advanceTime(1000)
underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 2500))
@@ -467,10 +497,13 @@
verify(windowManager, never()).addView(any(), any())
assertThat(underTest.activeViews.size).isEqualTo(0)
verify(configurationController).removeCallback(any())
+ assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2")
}
@Test
fun lowerThenHigherPriority_higherReplacesLower() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "normal",
@@ -499,10 +532,15 @@
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
verify(configurationController, never()).removeCallback(any())
+ // Since the controller is still storing the older view in case it'll get re-displayed
+ // later, the listener shouldn't be notified
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
}
@Test
fun lowerThenHigherPriority_lowerPriorityRedisplayed() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "normal",
@@ -537,6 +575,7 @@
// THEN the normal view is re-displayed
verify(windowManager).removeView(viewCaptor.allValues[1])
+ assertThat(listener.permanentlyRemovedIds).containsExactly("critical")
verify(windowManager).addView(any(), capture(windowParamsCaptor))
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
verify(configurationController, never()).removeCallback(any())
@@ -544,6 +583,8 @@
@Test
fun lowerThenHigherPriority_lowerPriorityNotRedisplayedBecauseTimedOut() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "normal",
@@ -573,6 +614,7 @@
verify(windowManager, never()).addView(any(), any())
assertThat(underTest.activeViews).isEmpty()
verify(configurationController).removeCallback(any())
+ assertThat(listener.permanentlyRemovedIds).containsExactly("critical", "normal")
}
@Test
@@ -609,6 +651,8 @@
@Test
fun higherThenLowerPriority_lowerEventuallyDisplayed() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "critical",
@@ -644,6 +688,7 @@
// THEN the second normal view is displayed
verify(windowManager).removeView(viewCaptor.value)
+ assertThat(listener.permanentlyRemovedIds).containsExactly("critical")
verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
assertThat(underTest.activeViews.size).isEqualTo(1)
@@ -652,6 +697,8 @@
@Test
fun higherThenLowerPriority_lowerNotDisplayedBecauseTimedOut() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "critical",
@@ -691,10 +738,13 @@
verify(windowManager, never()).addView(any(), any())
assertThat(underTest.activeViews).isEmpty()
verify(configurationController).removeCallback(any())
+ assertThat(listener.permanentlyRemovedIds).containsExactly("critical", "normal")
}
@Test
fun criticalThenNewCritical_newCriticalDisplayed() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "critical 1",
@@ -724,10 +774,15 @@
assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 2")
assertThat(underTest.activeViews.size).isEqualTo(2)
verify(configurationController, never()).removeCallback(any())
+ // Since the controller is still storing the older view in case it'll get re-displayed
+ // later, the listener shouldn't be notified
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
}
@Test
fun normalThenNewNormal_newNormalDisplayed() {
+ val listener = registerListener()
+
underTest.displayView(
ViewInfo(
name = "normal 1",
@@ -757,6 +812,9 @@
assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 2")
assertThat(underTest.activeViews.size).isEqualTo(2)
verify(configurationController, never()).removeCallback(any())
+ // Since the controller is still storing the older view in case it'll get re-displayed
+ // later, the listener shouldn't be notified
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
}
@Test
@@ -957,25 +1015,103 @@
}
@Test
- fun removeView_viewRemovedAndRemovalLogged() {
+ fun removeView_viewRemovedAndRemovalLoggedAndListenerNotified() {
+ val listener = registerListener()
+
// First, add the view
underTest.displayView(getState())
// Then, remove it
val reason = "test reason"
- val deviceId = "id"
- underTest.removeView(deviceId, reason)
+ underTest.removeView(DEFAULT_ID, reason)
verify(windowManager).removeView(any())
- verify(logger).logViewRemoval(deviceId, reason)
+ verify(logger).logViewRemoval(DEFAULT_ID, reason)
verify(configurationController).removeCallback(any())
+ assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
}
@Test
- fun removeView_noAdd_viewNotRemoved() {
+ fun removeView_noAdd_viewNotRemovedAndListenerNotNotified() {
+ val listener = registerListener()
+
underTest.removeView("id", "reason")
verify(windowManager, never()).removeView(any())
+ assertThat(listener.permanentlyRemovedIds).isEmpty()
+ }
+
+ @Test
+ fun listenerRegistered_notifiedOnRemoval() {
+ val listener = registerListener()
+ underTest.displayView(getState())
+
+ underTest.removeView(DEFAULT_ID, "reason")
+
+ assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+ }
+
+ @Test
+ fun listenerRegistered_notifiedOnTimedOutEvenWhenNotDisplayed() {
+ val listener = registerListener()
+ underTest.displayView(
+ ViewInfo(
+ id = "id1",
+ name = "name1",
+ timeoutMs = 3000,
+ ),
+ )
+
+ // Display a second view
+ underTest.displayView(
+ ViewInfo(
+ id = "id2",
+ name = "name2",
+ timeoutMs = 2500,
+ ),
+ )
+
+ // WHEN the second view times out
+ fakeClock.advanceTime(2501)
+
+ // THEN the listener is notified of both IDs, since id2 timed out and id1 doesn't have
+ // enough time left to be redisplayed
+ assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2")
+ }
+
+ @Test
+ fun multipleListeners_allNotified() {
+ val listener1 = registerListener()
+ val listener2 = registerListener()
+ val listener3 = registerListener()
+
+ underTest.displayView(getState())
+
+ underTest.removeView(DEFAULT_ID, "reason")
+
+ assertThat(listener1.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+ assertThat(listener2.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+ assertThat(listener3.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+ }
+
+ @Test
+ fun sameListenerRegisteredMultipleTimes_onlyNotifiedOnce() {
+ val listener = registerListener()
+ underTest.registerListener(listener)
+ underTest.registerListener(listener)
+
+ underTest.displayView(getState())
+
+ underTest.removeView(DEFAULT_ID, "reason")
+
+ assertThat(listener.permanentlyRemovedIds).hasSize(1)
+ assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+ }
+
+ private fun registerListener(): Listener {
+ return Listener().also {
+ underTest.registerListener(it)
+ }
}
private fun getState(name: String = "name") = ViewInfo(name)
@@ -1030,9 +1166,17 @@
override val windowTitle: String = "Window Title",
override val wakeReason: String = "WAKE_REASON",
override val timeoutMs: Int = TIMEOUT_MS.toInt(),
- override val id: String = "id",
+ override val id: String = DEFAULT_ID,
override val priority: ViewPriority = ViewPriority.NORMAL,
) : TemporaryViewInfo()
+
+ inner class Listener : TemporaryViewDisplayController.Listener {
+ val permanentlyRemovedIds = mutableListOf<String>()
+ override fun onInfoPermanentlyRemoved(id: String) {
+ permanentlyRemovedIds.add(id)
+ }
+ }
}
private const val TIMEOUT_MS = 10000L
+private const val DEFAULT_ID = "defaultId"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 90178c6..45eb1f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -20,6 +20,7 @@
import android.os.VibrationEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
@@ -43,6 +44,8 @@
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+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.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
@@ -54,6 +57,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -74,6 +78,7 @@
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler
private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
private lateinit var fakeWakeLock: WakeLockFake
private lateinit var fakeClock: FakeSystemClock
@@ -106,6 +111,7 @@
powerManager,
falsingManager,
falsingCollector,
+ swipeGestureHandler,
viewUtil,
vibratorHelper,
fakeWakeLockBuilder,
@@ -430,17 +436,101 @@
verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any())
}
+ @Test
+ fun swipeToDismiss_false_neverListensForGesture() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ allowSwipeToDismiss = false,
+ )
+ )
+
+ verify(swipeGestureHandler, never()).addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun swipeToDismiss_true_listensForGesture() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ allowSwipeToDismiss = true,
+ )
+ )
+
+ verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun swipeToDismiss_swipeOccurs_viewDismissed() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ allowSwipeToDismiss = true,
+ )
+ )
+ val view = getChipbarView()
+
+ val callbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
+ verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), capture(callbackCaptor))
+
+ callbackCaptor.value.invoke(MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0))
+
+ verify(windowManager).removeView(view)
+ }
+
+ @Test
+ fun swipeToDismiss_viewUpdatedToFalse_swipeOccurs_viewNotDismissed() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ allowSwipeToDismiss = true,
+ )
+ )
+ val view = getChipbarView()
+ val callbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
+ verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), capture(callbackCaptor))
+
+ // WHEN the view is updated to not allow swipe-to-dismiss
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ allowSwipeToDismiss = false,
+ )
+ )
+
+ // THEN the callback is removed
+ verify(swipeGestureHandler).removeOnGestureDetectedCallback(any())
+
+ // And WHEN the old callback is invoked
+ callbackCaptor.value.invoke(MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0))
+
+ // THEN it is ignored and view isn't removed
+ verify(windowManager, never()).removeView(view)
+ }
+
private fun createChipbarInfo(
startIcon: Icon,
text: Text,
endItem: ChipbarEndItem?,
vibrationEffect: VibrationEffect? = null,
+ allowSwipeToDismiss: Boolean = false,
): ChipbarInfo {
return ChipbarInfo(
TintedIcon(startIcon, tintAttr = null),
text,
endItem,
vibrationEffect,
+ allowSwipeToDismiss,
windowTitle = WINDOW_TITLE,
wakeReason = WAKE_REASON,
timeoutMs = TIMEOUT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index 4ef4e6c..ffac8f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -43,6 +43,7 @@
powerManager: PowerManager,
falsingManager: FalsingManager,
falsingCollector: FalsingCollector,
+ swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler,
viewUtil: ViewUtil,
vibratorHelper: VibratorHelper,
wakeLockBuilder: WakeLock.Builder,
@@ -59,6 +60,7 @@
powerManager,
falsingManager,
falsingCollector,
+ swipeChipbarAwayGestureHandler,
viewUtil,
vibratorHelper,
wakeLockBuilder,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt
new file mode 100644
index 0000000..a87a950
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.systemui.temporarydisplay.chipbar
+
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class SwipeChipbarAwayGestureHandlerTest : SysuiTestCase() {
+
+ private lateinit var underTest: SwipeChipbarAwayGestureHandler
+
+ @Before
+ fun setUp() {
+ underTest = SwipeChipbarAwayGestureHandler(context, mock())
+ }
+
+ @Test
+ fun startOfGestureIsWithinBounds_noViewFetcher_returnsFalse() {
+ assertThat(underTest.startOfGestureIsWithinBounds(createMotionEvent())).isFalse()
+ }
+
+ @Test
+ fun startOfGestureIsWithinBounds_usesViewFetcher_aboveBottom_returnsTrue() {
+ val view = createMockView()
+
+ underTest.setViewFetcher { view }
+
+ val motionEvent = createMotionEvent(y = VIEW_BOTTOM - 100f)
+ assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
+ }
+
+ @Test
+ fun startOfGestureIsWithinBounds_usesViewFetcher_slightlyBelowBottom_returnsTrue() {
+ val view = createMockView()
+
+ underTest.setViewFetcher { view }
+
+ val motionEvent = createMotionEvent(y = VIEW_BOTTOM + 20f)
+ assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
+ }
+
+ @Test
+ fun startOfGestureIsWithinBounds_usesViewFetcher_tooFarDown_returnsFalse() {
+ val view = createMockView()
+
+ underTest.setViewFetcher { view }
+
+ val motionEvent = createMotionEvent(y = VIEW_BOTTOM * 4f)
+ assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isFalse()
+ }
+
+ @Test
+ fun startOfGestureIsWithinBounds_viewFetcherReset_returnsFalse() {
+ val view = createMockView()
+
+ underTest.setViewFetcher { view }
+
+ val motionEvent = createMotionEvent(y = VIEW_BOTTOM - 100f)
+ assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
+
+ underTest.resetViewFetcher()
+ assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isFalse()
+ }
+
+ private fun createMotionEvent(y: Float = 0f): MotionEvent {
+ return MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, y, 0)
+ }
+
+ private fun createMockView(): View {
+ return mock<View>().also {
+ doAnswer { invocation ->
+ val out: Rect = invocation.getArgument(0)
+ out.set(0, 0, 0, VIEW_BOTTOM)
+ null
+ }
+ .whenever(it)
+ .getBoundsOnScreen(any())
+ }
+ }
+
+ private companion object {
+ const val VIEW_BOTTOM = 455
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 2a93fff..1710709 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -172,7 +172,7 @@
verify(mDumpManager).registerDumpable(any(), any());
verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
verify(mSecureSettings).registerContentObserverForUser(
- eq(Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)),
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
eq(false), mSettingsObserver.capture(), eq(UserHandle.USER_ALL)
);
}
@@ -790,15 +790,15 @@
reset(mResources);
when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().getS500());
when(mResources.getColor(eq(android.R.color.system_accent2_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().getS500());
when(mResources.getColor(eq(android.R.color.system_accent3_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().getS500());
when(mResources.getColor(eq(android.R.color.system_neutral1_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().getS500());
when(mResources.getColor(eq(android.R.color.system_neutral2_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().getS500());
// Defers event because we already have initial colors.
verify(mThemeOverlayApplier, never())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
index 51afbcb..2b86cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
@@ -38,6 +38,7 @@
},
/* activityManager = */ mock(),
/* activityStarter = */ mock(),
+ mock(),
)
@get:Rule val activityRule = ActivityScenarioRule(CreateUserActivityTestable::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
index 6bfc2f1..7d0d57b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
@@ -19,9 +19,12 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -35,6 +38,10 @@
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import org.junit.Test
import org.junit.runner.RunWith
@@ -231,6 +238,170 @@
}
}
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ThrottleFlowTest : SysuiTestCase() {
+
+ @Test
+ fun doesNotAffectEmissions_whenDelayAtLeastEqualToPeriod() = runTest {
+ // Arrange
+ val choreographer = createChoreographer(this)
+ val output = mutableListOf<Int>()
+ val collectJob = backgroundScope.launch {
+ flow {
+ emit(1)
+ delay(1000)
+ emit(2)
+ }.throttle(1000, choreographer.fakeClock).toList(output)
+ }
+
+ // Act
+ choreographer.advanceAndRun(0)
+
+ // Assert
+ assertThat(output).containsExactly(1)
+
+ // Act
+ choreographer.advanceAndRun(999)
+
+ // Assert
+ assertThat(output).containsExactly(1)
+
+ // Act
+ choreographer.advanceAndRun(1)
+
+ // Assert
+ assertThat(output).containsExactly(1, 2)
+
+ // Cleanup
+ collectJob.cancel()
+ }
+
+ @Test
+ fun delaysEmissions_withShorterThanPeriodDelay_untilPeriodElapses() = runTest {
+ // Arrange
+ val choreographer = createChoreographer(this)
+ val output = mutableListOf<Int>()
+ val collectJob = backgroundScope.launch {
+ flow {
+ emit(1)
+ delay(500)
+ emit(2)
+ }.throttle(1000, choreographer.fakeClock).toList(output)
+ }
+
+ // Act
+ choreographer.advanceAndRun(0)
+
+ // Assert
+ assertThat(output).containsExactly(1)
+
+ // Act
+ choreographer.advanceAndRun(500)
+ choreographer.advanceAndRun(499)
+
+ // Assert
+ assertThat(output).containsExactly(1)
+
+ // Act
+ choreographer.advanceAndRun(1)
+
+ // Assert
+ assertThat(output).containsExactly(1, 2)
+
+ // Cleanup
+ collectJob.cancel()
+ }
+
+ @Test
+ fun filtersAllButLastEmission_whenMultipleEmissionsInPeriod() = runTest {
+ // Arrange
+ val choreographer = createChoreographer(this)
+ val output = mutableListOf<Int>()
+ val collectJob = backgroundScope.launch {
+ flow {
+ emit(1)
+ delay(500)
+ emit(2)
+ delay(500)
+ emit(3)
+ }.throttle(1000, choreographer.fakeClock).toList(output)
+ }
+
+ // Act
+ choreographer.advanceAndRun(0)
+
+ // Assert
+ assertThat(output).containsExactly(1)
+
+ // Act
+ choreographer.advanceAndRun(500)
+ choreographer.advanceAndRun(499)
+
+ // Assert
+ assertThat(output).containsExactly(1)
+
+ // Act
+ choreographer.advanceAndRun(1)
+
+ // Assert
+ assertThat(output).containsExactly(1, 3)
+
+ // Cleanup
+ collectJob.cancel()
+ }
+
+ @Test
+ fun filtersAllButLastEmission_andDelaysIt_whenMultipleEmissionsInShorterThanPeriod() = runTest {
+ // Arrange
+ val choreographer = createChoreographer(this)
+ val output = mutableListOf<Int>()
+ val collectJob = backgroundScope.launch {
+ flow {
+ emit(1)
+ delay(500)
+ emit(2)
+ delay(250)
+ emit(3)
+ }.throttle(1000, choreographer.fakeClock).toList(output)
+ }
+
+ // Act
+ choreographer.advanceAndRun(0)
+
+ // Assert
+ assertThat(output).containsExactly(1)
+
+ // Act
+ choreographer.advanceAndRun(500)
+ choreographer.advanceAndRun(250)
+ choreographer.advanceAndRun(249)
+
+ // Assert
+ assertThat(output).containsExactly(1)
+
+ // Act
+ choreographer.advanceAndRun(1)
+
+ // Assert
+ assertThat(output).containsExactly(1, 3)
+
+ // Cleanup
+ collectJob.cancel()
+ }
+
+ private fun createChoreographer(testScope: TestScope) = object {
+ val fakeClock = FakeSystemClock()
+
+ fun advanceAndRun(millis: Long) {
+ fakeClock.advanceTime(millis)
+ testScope.advanceTimeBy(millis)
+ testScope.runCurrent()
+ }
+ }
+}
+
private fun <T> assertThatFlow(flow: Flow<T>) =
object {
suspend fun emitsExactly(vararg emissions: T) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
index d0420f7..729168a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
@@ -22,13 +22,17 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.NotificationManager;
+import android.os.UserHandle;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import org.junit.After;
import org.junit.Before;
@@ -48,6 +52,7 @@
private File mLeakDir;
private File mLeakDump;
private File mLeakHprof;
+ private UserTracker mUserTracker;
private NotificationManager mNotificationManager;
@Before
@@ -56,6 +61,9 @@
mLeakDump = new File(mLeakDir, LeakReporter.LEAK_DUMP);
mLeakHprof = new File(mLeakDir, LeakReporter.LEAK_HPROF);
+ mUserTracker = mock(UserTracker.class);
+ when(mUserTracker.getUserHandle()).thenReturn(
+ UserHandle.of(ActivityManager.getCurrentUser()));
mNotificationManager = mock(NotificationManager.class);
mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
@@ -65,7 +73,7 @@
return null;
}).when(mLeakDetector).dump(any(), any());
- mLeakReporter = new LeakReporter(mContext, mLeakDetector, "test@example.com");
+ mLeakReporter = new LeakReporter(mContext, mUserTracker, mLeakDetector, "test@example.com");
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 915ea1a..0663004 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
@@ -101,6 +102,8 @@
@Mock
private ActivityManager mActivityManager;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private DumpManager mDumpManager;
@@ -113,6 +116,7 @@
// Initial non-set value
when(mRingerModeLiveData.getValue()).thenReturn(-1);
when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
// Enable group volume adjustments
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
@@ -124,7 +128,7 @@
mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
- mActivityManager, mDumpManager, mCallback);
+ mActivityManager, mUserTracker, mDumpManager, mCallback);
mVolumeController.setEnableDialogs(true, true);
}
@@ -233,12 +237,13 @@
CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
+ UserTracker userTracker,
DumpManager dumpManager,
C callback) {
super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
notificationManager, optionalVibrator, iAudioService, accessibilityManager,
packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
- activityManager, dumpManager);
+ activityManager, userTracker, dumpManager);
mCallbacks = callback;
ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
new file mode 100644
index 0000000..b527861
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
@@ -0,0 +1,243 @@
+/*
+ * 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.systemui.wallet.controller
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.mockito.any
+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 java.util.ArrayList
+import kotlinx.coroutines.CoroutineScope
+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
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class WalletContextualSuggestionsControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var walletController: QuickAccessWalletController
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var mockContext: Context
+ @Captor private lateinit var broadcastReceiver: ArgumentCaptor<BroadcastReceiver>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(
+ broadcastDispatcher.broadcastFlow<List<String>?>(
+ any(),
+ isNull(),
+ any(),
+ any(),
+ any()
+ )
+ )
+ .thenCallRealMethod()
+
+ whenever(featureFlags.isEnabled(eq(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)))
+ .thenReturn(true)
+
+ whenever(CARD_1.cardId).thenReturn(ID_1)
+ whenever(CARD_2.cardId).thenReturn(ID_2)
+ whenever(CARD_3.cardId).thenReturn(ID_3)
+ }
+
+ @Test
+ fun `state - has wallet cards - received contextual cards`() = runTest {
+ setUpWalletClient(listOf(CARD_1, CARD_2))
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(
+ mockContext,
+ createContextualCardsIntent(listOf(ID_1, ID_2))
+ )
+
+ assertThat(latest()).containsExactly(CARD_1, CARD_2)
+ }
+
+ @Test
+ fun `state - no wallet cards - received contextual cards`() = runTest {
+ setUpWalletClient(emptyList())
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(
+ mockContext,
+ createContextualCardsIntent(listOf(ID_1, ID_2))
+ )
+
+ assertThat(latest()).isEmpty()
+ }
+
+ @Test
+ fun `state - has wallet cards - no contextual cards`() = runTest {
+ setUpWalletClient(listOf(CARD_1, CARD_2))
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(mockContext, createContextualCardsIntent(emptyList()))
+
+ assertThat(latest()).isEmpty()
+ }
+
+ @Test
+ fun `state - wallet cards error`() = runTest {
+ setUpWalletClient(shouldFail = true)
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(
+ mockContext,
+ createContextualCardsIntent(listOf(ID_1, ID_2))
+ )
+
+ assertThat(latest()).isEmpty()
+ }
+
+ @Test
+ fun `state - no contextual cards extra`() = runTest {
+ setUpWalletClient(listOf(CARD_1, CARD_2))
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(mockContext, Intent(INTENT_NAME))
+
+ assertThat(latest()).isEmpty()
+ }
+
+ @Test
+ fun `state - has wallet cards - received contextual cards - feature disabled`() = runTest {
+ whenever(featureFlags.isEnabled(eq(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)))
+ .thenReturn(false)
+ setUpWalletClient(listOf(CARD_1, CARD_2))
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verify(broadcastDispatcher, never()).broadcastFlow(any(), isNull(), any(), any())
+ assertThat(latest()).isNull()
+ }
+
+ private fun createWalletContextualSuggestionsController(
+ scope: CoroutineScope
+ ): WalletContextualSuggestionsController {
+ return WalletContextualSuggestionsController(
+ scope,
+ walletController,
+ broadcastDispatcher,
+ featureFlags
+ )
+ }
+
+ private fun verifyRegistered() {
+ verify(broadcastDispatcher)
+ .registerReceiver(capture(broadcastReceiver), any(), isNull(), isNull(), any(), any())
+ }
+
+ private fun createContextualCardsIntent(
+ ids: List<String> = emptyList(),
+ ): Intent {
+ val intent = Intent(INTENT_NAME)
+ intent.putStringArrayListExtra("cardIds", ArrayList(ids))
+ return intent
+ }
+
+ private fun setUpWalletClient(
+ cards: List<WalletCard> = emptyList(),
+ shouldFail: Boolean = false
+ ) {
+ whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+ with(
+ invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
+ ) {
+ if (shouldFail) {
+ onWalletCardRetrievalError(mock())
+ } else {
+ onWalletCardsRetrieved(GetWalletCardsResponse(cards, 0))
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val ID_1: String = "123"
+ private val CARD_1: WalletCard = mock()
+ private const val ID_2: String = "456"
+ private val CARD_2: WalletCard = mock()
+ private const val ID_3: String = "789"
+ private val CARD_3: WalletCard = mock()
+ private val INTENT_NAME: String = "WalletSuggestionsIntent"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 0fdcb95..31cce4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -32,13 +32,13 @@
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.intThat;
+import android.app.ActivityManager;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Surface;
@@ -49,6 +49,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -81,6 +82,8 @@
private Surface mSurface;
@Mock
private Context mMockContext;
+ @Mock
+ private UserTracker mUserTracker;
@Mock
private Bitmap mWallpaperBitmap;
@@ -108,13 +111,16 @@
when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
// set up wallpaper manager
- when(mWallpaperManager.getBitmapAsUser(eq(UserHandle.USER_CURRENT), anyBoolean()))
+ when(mWallpaperManager.getBitmapAsUser(eq(ActivityManager.getCurrentUser()), anyBoolean()))
.thenReturn(mWallpaperBitmap);
when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
// set up surface
when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
doNothing().when(mSurface).hwuiDestroy();
+
+ // set up UserTracker
+ when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
}
@Test
@@ -170,7 +176,7 @@
}
private ImageWallpaper createImageWallpaper() {
- return new ImageWallpaper(mFakeBackgroundExecutor) {
+ return new ImageWallpaper(mFakeBackgroundExecutor, mUserTracker) {
@Override
public Engine onCreateEngine() {
return new CanvasEngine() {
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 a537848..0a1e3e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -91,6 +91,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
@@ -361,7 +362,8 @@
mock(Handler.class),
mock(NotifPipelineFlags.class),
mock(KeyguardNotificationVisibilityProvider.class),
- mock(UiEventLogger.class)
+ mock(UiEventLogger.class),
+ mock(UserTracker.class)
);
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
mBubbleController = new TestableBubbleController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index e5316bc8..ceee0bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -24,6 +24,7 @@
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;
@@ -48,7 +49,8 @@
Handler mainHandler,
NotifPipelineFlags flags,
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker) {
super(contentResolver,
powerManager,
dreamManager,
@@ -61,7 +63,8 @@
mainHandler,
flags,
keyguardNotificationVisibilityProvider,
- uiEventLogger);
+ uiEventLogger,
+ userTracker);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 0dd1fc7..251014f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -67,7 +67,10 @@
_userHandle = UserHandle.of(_userId)
val copy = callbacks.toList()
- copy.forEach { it.onUserChanged(_userId, userContext) }
+ copy.forEach {
+ it.onUserChanging(_userId, userContext)
+ it.onUserChanged(_userId, userContext)
+ }
}
fun onProfileChanged() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index e660e1f2..4b97316 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -23,6 +23,8 @@
import android.os.UserHandle;
import android.util.Pair;
+import com.android.systemui.settings.UserTracker;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -57,6 +59,11 @@
}
@Override
+ public UserTracker getUserTracker() {
+ return null;
+ }
+
+ @Override
public void registerContentObserverForUser(Uri uri, boolean notifyDescendents,
ContentObserver settingsObserver, int userHandle) {
List<ContentObserver> observers;
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 3fa0ab6..e6abc4c 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -391,7 +391,7 @@
private boolean takeScreenshot() {
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ screenshotHelper.takeScreenshot(
WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
new Handler(Looper.getMainLooper()), null);
return true;
diff --git a/services/api/current.txt b/services/api/current.txt
index 3926b39..e66bf4d 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -180,8 +180,8 @@
method @Nullable public String getPrimaryCpuAbi();
method @Nullable public String getSeInfo();
method @Nullable public String getSecondaryCpuAbi();
+ method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies();
method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle);
- method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries();
method public boolean isApex();
method public boolean isPrivileged();
method public boolean isSystem();
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index b20ff37..2ee9174 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -19,6 +19,7 @@
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
+import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import static com.android.server.backup.UserBackupManagerService.WALLPAPER_PACKAGE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -56,8 +57,8 @@
public class BackupEligibilityRules {
private static final boolean DEBUG = false;
// List of system packages that are eligible for backup in non-system users.
- private static final Set<String> systemPackagesAllowedForAllUsers =
- Sets.newArraySet(PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME, WALLPAPER_PACKAGE);
+ private static final Set<String> systemPackagesAllowedForAllUsers = Sets.newArraySet(
+ PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME, WALLPAPER_PACKAGE, SETTINGS_PACKAGE);
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
new file mode 100644
index 0000000..56e777f
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
@@ -0,0 +1,25 @@
+/*
+ * 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.server.companion.datatransfer.contextsync;
+
+/** Callback for call metadata syncing. */
+public abstract class CallMetadataSyncCallback {
+
+ abstract void processCallControlAction(int crossDeviceCallId, int callControlAction);
+
+ abstract void requestCrossDeviceSync(int userId);
+}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
new file mode 100644
index 0000000..077fd2a
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -0,0 +1,252 @@
+/*
+ * 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.server.companion.datatransfer.contextsync;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+/** Data holder for a telecom call and additional metadata. */
+public class CrossDeviceCall {
+
+ private static final String TAG = "CrossDeviceCall";
+
+ private static final int APP_ICON_BITMAP_DIMENSION = 256;
+
+ private static final AtomicLong sNextId = new AtomicLong(1);
+
+ private final long mId;
+ private final Call mCall;
+ @VisibleForTesting boolean mIsEnterprise;
+ @VisibleForTesting boolean mIsOtt;
+ private String mCallingAppName;
+ private byte[] mCallingAppIcon;
+ private String mCallerDisplayName;
+ private int mStatus = android.companion.Telecom.Call.UNKNOWN_STATUS;
+ private String mContactDisplayName;
+ private boolean mIsMuted;
+ private final Set<Integer> mControls = new HashSet<>();
+
+ public CrossDeviceCall(PackageManager packageManager, Call call,
+ CallAudioState callAudioState) {
+ mId = sNextId.getAndIncrement();
+ mCall = call;
+ final String callingAppPackageName = call != null
+ ? call.getDetails().getAccountHandle().getComponentName().getPackageName() : null;
+ mIsOtt = call != null
+ && (call.getDetails().getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED)
+ == Call.Details.PROPERTY_SELF_MANAGED;
+ mIsEnterprise = call != null
+ && (call.getDetails().getCallProperties() & Call.Details.PROPERTY_ENTERPRISE_CALL)
+ == Call.Details.PROPERTY_ENTERPRISE_CALL;
+ try {
+ final ApplicationInfo applicationInfo = packageManager
+ .getApplicationInfo(callingAppPackageName,
+ PackageManager.ApplicationInfoFlags.of(0));
+ mCallingAppName = packageManager.getApplicationLabel(applicationInfo).toString();
+ mCallingAppIcon = renderDrawableToByteArray(
+ packageManager.getApplicationIcon(applicationInfo));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Could not get application info for package " + callingAppPackageName, e);
+ }
+ mIsMuted = callAudioState != null && callAudioState.isMuted();
+ if (call != null) {
+ updateCallDetails(call.getDetails());
+ }
+ }
+
+ private byte[] renderDrawableToByteArray(Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ // Can't recycle the drawable's bitmap, so handle separately
+ final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
+ if (bitmap.getWidth() > APP_ICON_BITMAP_DIMENSION
+ || bitmap.getHeight() > APP_ICON_BITMAP_DIMENSION) {
+ // Downscale, as the original drawable bitmap is too large.
+ final Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap,
+ APP_ICON_BITMAP_DIMENSION, APP_ICON_BITMAP_DIMENSION, /* filter= */ true);
+ final byte[] renderedBitmap = renderBitmapToByteArray(scaledBitmap);
+ scaledBitmap.recycle();
+ return renderedBitmap;
+ }
+ return renderBitmapToByteArray(bitmap);
+ }
+ final Bitmap bitmap = Bitmap.createBitmap(APP_ICON_BITMAP_DIMENSION,
+ APP_ICON_BITMAP_DIMENSION,
+ Bitmap.Config.ARGB_8888);
+ try {
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ drawable.draw(canvas);
+ } finally {
+ bitmap.recycle();
+ }
+ return renderBitmapToByteArray(bitmap);
+ }
+
+ private byte[] renderBitmapToByteArray(Bitmap bitmap) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream(bitmap.getByteCount());
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+ return baos.toByteArray();
+ }
+
+ /**
+ * Update the mute state of this call. No-op if the call is not capable of being muted.
+ *
+ * @param isMuted true if the call should be muted, and false if the call should be unmuted.
+ */
+ public void updateMuted(boolean isMuted) {
+ mIsMuted = isMuted;
+ updateCallDetails(mCall.getDetails());
+ }
+
+ /**
+ * Update the state of the call to be ringing silently if it is currently ringing. No-op if the
+ * call is not
+ * currently ringing.
+ */
+ public void updateSilencedIfRinging() {
+ if (mStatus == android.companion.Telecom.Call.RINGING) {
+ mStatus = android.companion.Telecom.Call.RINGING_SILENCED;
+ }
+ mControls.remove(android.companion.Telecom.Call.SILENCE);
+ }
+
+ @VisibleForTesting
+ void updateCallDetails(Call.Details callDetails) {
+ mCallerDisplayName = callDetails.getCallerDisplayName();
+ mContactDisplayName = callDetails.getContactDisplayName();
+ mStatus = convertStateToStatus(callDetails.getState());
+ mControls.clear();
+ if (mStatus == android.companion.Telecom.Call.RINGING
+ || mStatus == android.companion.Telecom.Call.RINGING_SILENCED) {
+ mControls.add(android.companion.Telecom.Call.ACCEPT);
+ mControls.add(android.companion.Telecom.Call.REJECT);
+ if (mStatus == android.companion.Telecom.Call.RINGING) {
+ mControls.add(android.companion.Telecom.Call.SILENCE);
+ }
+ }
+ if (mStatus == android.companion.Telecom.Call.ONGOING
+ || mStatus == android.companion.Telecom.Call.ON_HOLD) {
+ mControls.add(android.companion.Telecom.Call.END);
+ if (callDetails.can(Call.Details.CAPABILITY_HOLD)) {
+ mControls.add(
+ mStatus == android.companion.Telecom.Call.ON_HOLD
+ ? android.companion.Telecom.Call.TAKE_OFF_HOLD
+ : android.companion.Telecom.Call.PUT_ON_HOLD);
+ }
+ }
+ if (mStatus == android.companion.Telecom.Call.ONGOING && callDetails.can(
+ Call.Details.CAPABILITY_MUTE)) {
+ mControls.add(mIsMuted ? android.companion.Telecom.Call.UNMUTE
+ : android.companion.Telecom.Call.MUTE);
+ }
+ }
+
+ private int convertStateToStatus(int callState) {
+ switch (callState) {
+ case Call.STATE_HOLDING:
+ return android.companion.Telecom.Call.ON_HOLD;
+ case Call.STATE_ACTIVE:
+ return android.companion.Telecom.Call.ONGOING;
+ case Call.STATE_RINGING:
+ return android.companion.Telecom.Call.RINGING;
+ case Call.STATE_NEW:
+ case Call.STATE_DIALING:
+ case Call.STATE_DISCONNECTED:
+ case Call.STATE_SELECT_PHONE_ACCOUNT:
+ case Call.STATE_CONNECTING:
+ case Call.STATE_DISCONNECTING:
+ case Call.STATE_PULLING_CALL:
+ case Call.STATE_AUDIO_PROCESSING:
+ case Call.STATE_SIMULATED_RINGING:
+ default:
+ return android.companion.Telecom.Call.UNKNOWN_STATUS;
+ }
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public Call getCall() {
+ return mCall;
+ }
+
+ public String getCallingAppName() {
+ return mCallingAppName;
+ }
+
+ public byte[] getCallingAppIcon() {
+ return mCallingAppIcon;
+ }
+
+ /**
+ * Get a human-readable "caller id" to display as the origin of the call.
+ *
+ * @param isAdminBlocked whether there is an admin that has blocked contacts over Bluetooth
+ */
+ public String getReadableCallerId(boolean isAdminBlocked) {
+ if (mIsOtt) {
+ return mCallerDisplayName;
+ }
+ return mIsEnterprise && isAdminBlocked ? mCallerDisplayName : mContactDisplayName;
+ }
+
+ public int getStatus() {
+ return mStatus;
+ }
+
+ public Set<Integer> getControls() {
+ return mControls;
+ }
+
+ void doAccept() {
+ mCall.answer(VideoProfile.STATE_AUDIO_ONLY);
+ }
+
+ void doReject() {
+ if (mStatus == android.companion.Telecom.Call.RINGING) {
+ mCall.reject(Call.REJECT_REASON_DECLINED);
+ }
+ }
+
+ void doEnd() {
+ mCall.disconnect();
+ }
+
+ void doPutOnHold() {
+ mCall.hold();
+ }
+
+ void doTakeOffHold() {
+ mCall.unhold();
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index db163dc..5985ce4 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -34,6 +34,7 @@
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.companion.virtual.VirtualDeviceParams;
@@ -119,6 +120,7 @@
private final VirtualDeviceParams mParams;
private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
private final IVirtualDeviceActivityListener mActivityListener;
+ private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
@@ -170,6 +172,7 @@
OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
+ IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
VirtualDeviceParams params) {
this(
@@ -184,6 +187,7 @@
onDeviceCloseListener,
pendingTrampolineCallback,
activityListener,
+ soundEffectListener,
runningAppsChangedCallback,
params);
}
@@ -201,6 +205,7 @@
OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
+ IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
VirtualDeviceParams params) {
super(PermissionEnforcer.fromContext(context));
@@ -209,6 +214,7 @@
mAssociationInfo = associationInfo;
mPendingTrampolineCallback = pendingTrampolineCallback;
mActivityListener = activityListener;
+ mSoundEffectListener = soundEffectListener;
mRunningAppsChangedCallback = runningAppsChangedCallback;
mOwnerUid = ownerUid;
mDeviceId = deviceId;
@@ -937,6 +943,14 @@
Toast.LENGTH_LONG, mContext.getMainLooper());
}
+ void playSoundEffect(int effectType) {
+ try {
+ mSoundEffectListener.onPlaySoundEffect(effectType);
+ } catch (RemoteException exception) {
+ Slog.w(TAG, "Unable to invoke sound effect listener", exception);
+ }
+ }
+
/**
* Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true if
* the intent matches any filter notifying the DisplayPolicyController to abort the
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b0f2464..2395814c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -30,6 +30,7 @@
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
@@ -175,6 +176,11 @@
@VisibleForTesting
void notifyRunningAppsChanged(int deviceId, ArraySet<Integer> uids) {
synchronized (mVirtualDeviceManagerLock) {
+ if (!mVirtualDevices.contains(deviceId)) {
+ Slog.e(TAG, "notifyRunningAppsChanged called for unknown deviceId:" + deviceId
+ + " (maybe it was recently closed?)");
+ return;
+ }
mAppsOnVirtualDevices.put(deviceId, uids);
}
mLocalService.onAppsOnVirtualDeviceChanged();
@@ -222,7 +228,8 @@
String packageName,
int associationId,
@NonNull VirtualDeviceParams params,
- @NonNull IVirtualDeviceActivityListener activityListener) {
+ @NonNull IVirtualDeviceActivityListener activityListener,
+ @NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"createVirtualDevice");
@@ -246,7 +253,7 @@
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
associationInfo, token, callingUid, deviceId, cameraAccessController,
this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
- runningAppsChangedCallback, params);
+ soundEffectListener, runningAppsChangedCallback, params);
mVirtualDevices.put(deviceId, virtualDevice);
return virtualDevice;
}
@@ -364,6 +371,18 @@
}
}
+ @Override // Binder call
+ public void playSoundEffect(int deviceId, int effectType) {
+ VirtualDeviceImpl virtualDevice;
+ synchronized (mVirtualDeviceManagerLock) {
+ virtualDevice = mVirtualDevices.get(deviceId);
+ }
+
+ if (virtualDevice != null) {
+ virtualDevice.playSoundEffect(effectType);
+ }
+ }
+
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final UserHandle userHandle = getCallingUserHandle();
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 67f5f1b..fce44f5 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1074,11 +1074,6 @@
// register a package observer to detect updates to preloads
mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
@Override
- public void onPackageAdded(String packageName, int uid) {
-
- }
-
- @Override
public void onPackageChanged(String packageName, int uid) {
// check if the updated package is a preloaded app.
PackageManager pm = mContext.getPackageManager();
@@ -1094,11 +1089,6 @@
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
}
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
-
- }
});
// TODO(b/264428429): Register observer for updates to APEXs.
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index ff75796..4854c37 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -321,6 +321,7 @@
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>();
+ private final ArraySet<String> mAutomaticRollbackDenylistedPackages = new ArraySet<>();
private final ArraySet<String> mWhitelistedStagedInstallers = new ArraySet<>();
// A map from package name of vendor APEXes that can be updated to an installer package name
// allowed to install updates for it.
@@ -461,6 +462,10 @@
return mRollbackWhitelistedPackages;
}
+ public Set<String> getAutomaticRollbackDenylistedPackages() {
+ return mAutomaticRollbackDenylistedPackages;
+ }
+
public Set<String> getWhitelistedStagedInstallers() {
return mWhitelistedStagedInstallers;
}
@@ -1356,6 +1361,16 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "automatic-rollback-denylisted-app": {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mAutomaticRollbackDenylistedPackages.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "whitelisted-staged-installer": {
if (allowAppConfigs) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 35b5f1b..c16314b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1491,13 +1491,7 @@
Account[] sharedAccounts = getSharedAccountsAsUser(userId);
if (sharedAccounts == null || sharedAccounts.length == 0) return;
Account[] accounts = getAccountsAsUser(null, userId, mContext.getOpPackageName());
- int parentUserId = UserManager.isSplitSystemUser()
- ? getUserManager().getUserInfo(userId).restrictedProfileParentId
- : UserHandle.USER_SYSTEM;
- if (parentUserId < 0) {
- Log.w(TAG, "User " + userId + " has shared accounts, but no parent user");
- return;
- }
+ int parentUserId = UserHandle.USER_SYSTEM;
for (Account sa : sharedAccounts) {
if (ArrayUtils.contains(accounts, sa)) continue;
// Account doesn't exist. Copy it now.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6719fdb..73bb8d7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -206,6 +206,7 @@
import com.android.server.SystemService;
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.LowMemDetector.MemFactor;
+import com.android.server.am.ServiceRecord.ShortFgsInfo;
import com.android.server.pm.KnownPackages;
import com.android.server.uri.NeededUriGrants;
import com.android.server.wm.ActivityServiceConnectionsHolder;
@@ -1940,6 +1941,12 @@
ignoreForeground = true;
}
+ // Whether FGS-BG-start restriction is enabled for this service.
+ final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r);
+
+ // Whether to extend the SHORT_SERVICE time out.
+ boolean extendShortServiceTimeout = false;
+
int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
if (!ignoreForeground) {
if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
@@ -1955,41 +1962,75 @@
"startForeground(SHORT_SERVICE) called on a service that's not"
+ " started.");
}
- // If the service is already an FGS, and the type is changing, then we
- // may need to do some extra work here.
- if (r.isForeground && (r.foregroundServiceType != foregroundServiceType)) {
- // TODO(short-service): Consider transitions:
- // A. Short -> other types:
- // Apply the BG restriction again. Don't just allow it.
- // i.e. unless the app is in a situation where it's allowed to start
- // a FGS, this transition shouldn't be allowed.
- // ... But think about it more, there may be a case this should be
- // allowed.
- //
- // If the transition is allowed, stop the timeout.
- // If the transition is _not_ allowed... keep the timeout?
- //
- // B. Short -> Short:
- // Allowed, but the timeout won't reset. The original timeout is used.
- // C. Other -> short:
- // This should always be allowed.
- // A timeout should start.
- // For now, let's just disallow transition from / to SHORT_SERVICE.
- final boolean isNewTypeShortFgs =
- foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- if (r.isShortFgs() != isNewTypeShortFgs) {
- // TODO(short-service): We should (probably) allow it.
- throw new IllegalArgumentException(
- "setForeground(): Changing foreground service type from / to "
- + " SHORT_SERVICE is now allowed");
+ // Side note: If a valid short-service (which has to be "started"), happens to
+ // also be bound, then we still _will_ apply a timeout, because it still has
+ // to be stopped.
+
+ // Calling startForeground on a SHORT_SERVICE will require some additional
+ // checks.
+ // A) SHORT_SERVICE -> another type.
+ // - This should be allowed only when the app could start another FGS.
+ // - When succeed, the timeout should stop.
+ // B) SHORT_SERVICE -> SHORT_SERVICE
+ // - If the app could start an FGS, then this would extend the timeout.
+ // - Otherwise, it's basically a no-op.
+ // - If it's already timed out, we also throw.
+ // Also,
+ // C) another type -> SHORT_SERVICE
+ // - This will always be allowed.
+ // - Timeout will start.
+
+ final boolean isOldTypeShortFgs = r.isShortFgs();
+ final boolean isNewTypeShortFgs =
+ foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ final boolean isOldTypeShortFgsAndTimedOut = r.shouldTriggerShortFgsTimeout();
+
+ if (r.isForeground && (isOldTypeShortFgs || isNewTypeShortFgs)) {
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.i(TAG_SERVICE, String.format(
+ "FGS type changing from %x%s to %x: %s",
+ r.foregroundServiceType,
+ (isOldTypeShortFgsAndTimedOut ? "(timed out short FGS)" : ""),
+ foregroundServiceStartType,
+ r.toString()));
}
}
- // If a valid short-service (which has to be "started"), happens to
- // also be bound, then we still _will_ apply a timeout, because it still has
- // to be stopped.
- if (r.mStartForegroundCount == 0) {
+ if (r.isForeground && isOldTypeShortFgs) {
+ // If we get here, that means startForeground(SHORT_SERVICE) is called again
+ // on a SHORT_SERVICE FGS.
+
+ // See if the app could start an FGS or not.
+ r.mAllowStartForeground = REASON_DENIED;
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+ BackgroundStartPrivileges.NONE,
+ false /* isBindService */);
+
+ final boolean fgsStartAllowed =
+ !isBgFgsRestrictionEnabledForService
+ || (r.mAllowStartForeground != REASON_DENIED);
+
+ if (fgsStartAllowed) {
+ if (isNewTypeShortFgs) {
+ // Only in this case, we extend the SHORT_SERVICE time out.
+ extendShortServiceTimeout = true;
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.i(TAG_SERVICE, "Extending SHORT_SERVICE time out: " + r);
+ }
+ } else {
+ // FGS type is changing from SHORT_SERVICE to another type when
+ // an app is allowed to start FGS, so this will succeed.
+ // The timeout will stop later, in
+ // maybeUpdateShortFgsTrackingLocked().
+ }
+ } else {
+ // We catch this case later, in the
+ // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
+ }
+
+ } else if (r.mStartForegroundCount == 0) {
/*
If the service was started with startService(), not
startForegroundService(), and if startForeground() isn't called within
@@ -2032,6 +2073,7 @@
BackgroundStartPrivileges.NONE,
false /* isBindService */);
}
+
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
if (!r.mAllowWhileInUsePermissionInFgs) {
@@ -2041,10 +2083,12 @@
+ r.shortInstanceName);
}
logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
+ if (r.mAllowStartForeground == REASON_DENIED
+ && isBgFgsRestrictionEnabledForService) {
final String msg = "Service.startForeground() not allowed due to "
+ "mAllowStartForeground false: service "
- + r.shortInstanceName;
+ + r.shortInstanceName
+ + (isOldTypeShortFgs ? " (Called on SHORT_SERVICE)" : "");
Slog.w(TAG, msg);
showFgsBgRestrictedNotificationLocked(r);
updateServiceForegroundLocked(psr, true);
@@ -2181,11 +2225,8 @@
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
- // Note, we'll get here if setForeground(SHORT_SERVICE) is called on a
- // already short-fgs.
- // In that case, because ShortFgsInfo is already set, this method
- // will be noop.
- maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r);
+ maybeUpdateShortFgsTrackingLocked(r,
+ extendShortServiceTimeout);
} else {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2980,38 +3021,51 @@
}
/**
- * If {@code sr} is of a short-fgs, start a short-FGS timeout.
+ * Update a {@link ServiceRecord}'s {@link ShortFgsInfo} as needed, and also start
+ * a timeout as needed.
+ *
+ * If the {@link ServiceRecord} is not a short-FGS, then we'll stop the timeout and clear
+ * the {@link ShortFgsInfo}.
*/
- private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr) {
+ private void maybeUpdateShortFgsTrackingLocked(ServiceRecord sr,
+ boolean extendTimeout) {
if (!sr.isShortFgs()) {
+ sr.clearShortFgsInfo(); // Just in case we have it.
+ unscheduleShortFgsTimeoutLocked(sr);
return;
}
if (DEBUG_SHORT_SERVICE) {
Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
}
- if (sr.hasShortFgsInfo()) {
- sr.getShortFgsInfo().update();
- } else {
- sr.setShortFgsInfo(SystemClock.uptimeMillis());
- }
- unscheduleShortFgsTimeoutLocked(sr); // Do it just in case
- final Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
- mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getTimeoutTime());
+ if (extendTimeout || !sr.hasShortFgsInfo()) {
+ sr.setShortFgsInfo(SystemClock.uptimeMillis());
+
+ // We'll restart the timeout.
+ unscheduleShortFgsTimeoutLocked(sr);
+
+ final Message msg = mAm.mHandler.obtainMessage(
+ ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
+ mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getTimeoutTime());
+ } else {
+ // We only (potentially) update the start command, start count, but not the timeout
+ // time.
+ // In this case, we keep the existing timeout running.
+ sr.getShortFgsInfo().update();
+ }
}
/**
* Stop the timeout for a ServiceRecord, if it's of a short-FGS.
*/
private void maybeStopShortFgsTimeoutLocked(ServiceRecord sr) {
+ sr.clearShortFgsInfo(); // Always clear, just in case.
if (!sr.isShortFgs()) {
return;
}
if (DEBUG_SHORT_SERVICE) {
Slog.i(TAG_SERVICE, "Stop short FGS timeout: " + sr);
}
- sr.clearShortFgsInfo();
unscheduleShortFgsTimeoutLocked(sr);
}
@@ -7562,7 +7616,8 @@
if (!r.mLoggedInfoAllowStartForeground) {
final String msg = "Background started FGS: "
+ ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
- + r.mInfoAllowStartForeground;
+ + r.mInfoAllowStartForeground
+ + (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : "");
if (r.mAllowStartForeground != REASON_DENIED) {
if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index abaa8c7..31ea092 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -75,8 +75,9 @@
* @param conn Receives information as the service is started and stopped.
* This must be a valid ServiceConnection object; it must not be null.
* @param clientAppUid Uid of the app for which the sdk sandbox process needs to be spawned.
- * @param clientApplicationThread ApplicationThread object of the app for which the sdk sandboox
- * is spawned.
+ * @param clientAppProcessToken process token used to uniquely identify the client app
+ * process binding to the SDK sandbox. This is obtained using
+ * {@link Context#getProcessToken()}.
* @param clientAppPackage Package of the app for which the sdk sandbox process needs to
* be spawned. This package must belong to the clientAppUid.
* @param processName Unique identifier for the service instance. Each unique name here will
@@ -92,7 +93,7 @@
*/
@SuppressLint("RethrowRemoteException")
boolean bindSdkSandboxService(@NonNull Intent service, @NonNull ServiceConnection conn,
- int clientAppUid, @NonNull IBinder clientApplicationThread,
+ int clientAppUid, @NonNull IBinder clientAppProcessToken,
@NonNull String clientAppPackage, @NonNull String processName,
@Context.BindServiceFlags int flags)
throws RemoteException;
@@ -112,9 +113,9 @@
/**
* Kill an app process associated with an SDK sandbox.
*
- * @param clientApplicationThreadBinder binder value of the
- * {@link android.app.IApplicationThread} of a client app process associated with a
- * sandbox. This is obtained using {@link Context#getIApplicationThreadBinder()}.
+ * @param clientAppProcessToken process token used to uniquely identify the client app
+ * process associated with an SDK sandbox. This is obtained using
+ * {@link Context#getProcessToken()}.
*/
- void killSdkSandboxClientAppProcess(@NonNull IBinder clientApplicationThreadBinder);
+ void killSdkSandboxClientAppProcess(@NonNull IBinder clientAppProcessToken);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d2b50f6..de87a0c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1606,8 +1606,6 @@
// Encapsulates the global setting "hidden_api_blacklist_exemptions"
final HiddenApiSettings mHiddenApiBlacklist;
- final SdkSandboxSettings mSdkSandboxSettings;
-
private final PlatformCompat mPlatformCompat;
PackageManagerInternal mPackageManagerInt;
@@ -2324,53 +2322,6 @@
}
}
- /**
- * Handles settings related to the enforcement of SDK sandbox restrictions.
- */
- static class SdkSandboxSettings implements DeviceConfig.OnPropertiesChangedListener {
-
- private final Context mContext;
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private boolean mEnforceBroadcastReceiverRestrictions;
-
- /**
- * Property to enforce broadcast receiver restrictions for SDK sandbox processes. If the
- * value of this property is {@code true}, the restrictions will be enforced.
- */
- public static final String ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS =
- "enforce_broadcast_receiver_restrictions";
-
- SdkSandboxSettings(Context context) {
- mContext = context;
- }
-
- void registerObserver() {
- synchronized (mLock) {
- mEnforceBroadcastReceiverRestrictions = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SDK_SANDBOX,
- ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS, false);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SDK_SANDBOX,
- mContext.getMainExecutor(), this);
- }
- }
-
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- synchronized (mLock) {
- mEnforceBroadcastReceiverRestrictions = properties.getBoolean(
- ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS, false);
- }
- }
-
- boolean isBroadcastReceiverRestrictionsEnforced() {
- synchronized (mLock) {
- return mEnforceBroadcastReceiverRestrictions;
- }
- }
- }
-
AppOpsManager getAppOpsManager() {
if (mAppOpsManager == null) {
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
@@ -2414,7 +2365,6 @@
mProcStartHandlerThread = null;
mProcStartHandler = null;
mHiddenApiBlacklist = null;
- mSdkSandboxSettings = null;
mFactoryTest = FACTORY_TEST_OFF;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mInternal = new LocalService();
@@ -2539,7 +2489,6 @@
mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext);
- mSdkSandboxSettings = new SdkSandboxSettings(mContext);
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
@@ -8300,7 +8249,6 @@
final boolean alwaysFinishActivities =
Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
mHiddenApiBlacklist.registerObserver();
- mSdkSandboxSettings.registerObserver();
mPlatformCompat.registerContentObserver();
mAppProfiler.retrieveSettings();
@@ -8471,10 +8419,7 @@
// Enable home activity for system user, so that the system can always boot. We don't
// do this when the system user is not setup since the setup wizard should be the one
// to handle home activity in this case.
- if (UserManager.isSplitSystemUser() &&
- Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE, 0, currentUserId) != 0
- || SystemProperties.getBoolean(SYSTEM_USER_HOME_NEEDED, false)) {
+ if (SystemProperties.getBoolean(SYSTEM_USER_HOME_NEEDED, false)) {
t.traceBegin("enableHomeActivity");
ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
try {
@@ -13545,16 +13490,6 @@
String callerFeatureId, String receiverId, IIntentReceiver receiver,
IntentFilter filter, String permission, int userId, int flags) {
enforceNotIsolatedCaller("registerReceiver");
-
- // Allow Sandbox process to register only unexported receivers.
- boolean unexported = (flags & Context.RECEIVER_NOT_EXPORTED) != 0;
- if (mSdkSandboxSettings.isBroadcastReceiverRestrictionsEnforced()
- && Process.isSdkSandboxUid(Binder.getCallingUid())
- && !unexported) {
- throw new SecurityException("SDK sandbox process not allowed to call "
- + "registerReceiver");
- }
-
ArrayList<Intent> stickyIntents = null;
ProcessRecord callerApp = null;
final boolean visibleToInstantApps
@@ -13618,6 +13553,20 @@
}
}
+ if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+ SdkSandboxManagerLocal sdkSandboxManagerLocal =
+ LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+ if (sdkSandboxManagerLocal == null) {
+ throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
+ + " whether SDK sandbox uid can register to broadcast receivers.");
+ }
+ if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
+ /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
+ throw new SecurityException("SDK sandbox not allowed to register receiver"
+ + " with the given IntentFilter");
+ }
+ }
+
// If the change is enabled, but neither exported or not exported is set, we need to log
// an error so the consumer can know to explicitly set the value for their flag.
// If the caller is registering for a sticky broadcast with a null receiver, we won't
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 5b453b2..0327d16 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -380,7 +380,7 @@
}
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
mBatteryUsageStatsStore);
- mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map);
+ mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
}
public void publish() {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f54e2b0..410bc41 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -118,7 +118,7 @@
private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
// Defaults for phenotype flags.
- @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
+ @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = true;
@VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
@VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_ALL;
@VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index d2fb7b5..491abc4 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -57,6 +57,7 @@
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
@@ -83,11 +84,12 @@
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.RescueParty;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerService;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -124,13 +126,6 @@
ContentProviderHolder getContentProvider(IApplicationThread caller, String callingPackage,
String name, int userId, boolean stable) {
mService.enforceNotIsolatedCaller("getContentProvider");
- if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
- // TODO(b/226318628): for sdk sandbox processes only allow accessing CPs registered by
- // the WebView apk.
- Slog.w(TAG, "Sdk sandbox process " + Binder.getCallingUid()
- + " is accessing content provider " + name
- + ". This access will most likely be blocked in the future");
- }
if (caller == null) {
String msg = "null IApplicationThread when getting content provider " + name;
Slog.w(TAG, msg);
@@ -191,7 +186,7 @@
checkTime(startTime, "getContentProviderImpl: getProviderByName");
- UserManagerService userManagerService = UserManagerService.getInstance();
+ UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
/*
For clone user profile and allowed authority, skipping finding provider and redirecting
@@ -201,8 +196,10 @@
used and redirect to owner user's MediaProvider.
*/
//todo(b/236121588) MediaProvider should not be installed in clone profile.
- if (!isAuthorityRedirectedForCloneProfile(name)
- || !userManagerService.isMediaSharedWithParent(userId)) {
+ final UserProperties userProps = umInternal.getUserProperties(userId);
+ final boolean isMediaSharedWithParent =
+ userProps != null && userProps.getIsMediaSharedWithParent();
+ if (!isAuthorityRedirectedForCloneProfile(name) || !isMediaSharedWithParent) {
// First check if this content provider has been published...
cpr = mProviderMap.getProviderByName(name, userId);
}
@@ -220,9 +217,7 @@
userId = UserHandle.USER_SYSTEM;
checkCrossUser = false;
} else if (isAuthorityRedirectedForCloneProfile(name)) {
- if (userManagerService.isMediaSharedWithParent(userId)) {
- UserManagerInternal umInternal = LocalServices.getService(
- UserManagerInternal.class);
+ if (isMediaSharedWithParent) {
userId = umInternal.getProfileParentId(userId);
checkCrossUser = false;
}
@@ -255,6 +250,7 @@
if (r != null && cpr.canRunHere(r)) {
checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
cpr.name.flattenToShortString(), startTime);
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
// This provider has been published or is in the process
// of being published... but it is also allowed to run
@@ -447,6 +443,7 @@
// info and allow the caller to instantiate it. Only do
// this if the provider is the same user as the caller's
// process, or can run as root (so can be in any process).
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
return cpr.newHolder(null, true);
}
@@ -589,6 +586,8 @@
// Return a holder instance even if we are waiting for the publishing of the
// provider, client will check for the holder.provider to see if it needs to wait
// for it.
+ //todo(b/265965249) Need to perform cleanup before calling enforce method here
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
return cpr.newHolder(conn, false);
}
}
@@ -650,6 +649,7 @@
+ " caller=" + callerName + "/" + Binder.getCallingUid());
return null;
}
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
return cpr.newHolder(conn, false);
}
@@ -1230,6 +1230,7 @@
appName = r.toString();
}
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
return checkContentProviderPermission(cpi, callingPid, Binder.getCallingUid(),
userId, checkUser, appName);
}
@@ -1998,6 +1999,26 @@
}
}
+ // Binder.clearCallingIdentity() shouldn't be called before this method
+ // as Binder should have its original callingUid for the check
+ private void enforceContentProviderRestrictionsForSdkSandbox(ProviderInfo cpi) {
+ if (!Process.isSdkSandboxUid(Binder.getCallingUid())) {
+ return;
+ }
+ final SdkSandboxManagerLocal sdkSandboxManagerLocal =
+ LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+ if (sdkSandboxManagerLocal == null) {
+ throw new IllegalStateException("SdkSandboxManagerLocal not found "
+ + "when checking whether SDK sandbox uid may "
+ + "access the contentprovider.");
+ }
+ if (!sdkSandboxManagerLocal
+ .canAccessContentProviderFromSdkSandbox(cpi)) {
+ throw new SecurityException(
+ "SDK sandbox uid may not access contentprovider " + cpi.name);
+ }
+ }
+
/**
* There are three ways to call this:
* - no provider specified: dump all the providers
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 9bb63d3..58a47d7 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -49,6 +49,7 @@
import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.expresslog.Counter;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.anr.AnrLatencyTracker;
@@ -260,6 +261,28 @@
mDialogController = new ErrorDialogController(app);
}
+ @GuardedBy("mService")
+ boolean skipAnrLocked(String annotation) {
+ // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
+ if (mService.mAtmInternal.isShuttingDown()) {
+ Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
+ return true;
+ } else if (isNotResponding()) {
+ Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
+ return true;
+ } else if (isCrashing()) {
+ Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
+ return true;
+ } else if (mApp.isKilledByAm()) {
+ Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
+ return true;
+ } else if (mApp.isKilled()) {
+ Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
+ return true;
+ }
+ return false;
+ }
+
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, TimeoutRecord timeoutRecord,
@@ -303,26 +326,10 @@
// Store annotation here as instance above will not be hit on all paths.
setAnrAnnotation(annotation);
- // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
- if (mService.mAtmInternal.isShuttingDown()) {
- Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
+ Counter.logIncrement("stability_anr.value_total_anrs");
+ if (skipAnrLocked(annotation)) {
latencyTracker.anrSkippedProcessErrorStateRecordAppNotResponding();
- return;
- } else if (isNotResponding()) {
- Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
- latencyTracker.anrSkippedProcessErrorStateRecordAppNotResponding();
- return;
- } else if (isCrashing()) {
- Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
- latencyTracker.anrSkippedProcessErrorStateRecordAppNotResponding();
- return;
- } else if (mApp.isKilledByAm()) {
- Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
- latencyTracker.anrSkippedProcessErrorStateRecordAppNotResponding();
- return;
- } else if (mApp.isKilled()) {
- Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
- latencyTracker.anrSkippedProcessErrorStateRecordAppNotResponding();
+ Counter.logIncrement("stability_anr.value_skipped_anrs");
return;
}
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 4e1d1ca..c6a8bcd 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -90,7 +90,7 @@
]
},
{
- "file_patterns": ["Broadcast"],
+ "file_patterns": ["Broadcast.*"],
"name": "FrameworksMockingServicesTests",
"options": [
{ "include-filter": "com.android.server.am.BroadcastRecordTest" },
@@ -99,7 +99,7 @@
]
},
{
- "file_patterns": ["Broadcast"],
+ "file_patterns": ["Broadcast.*"],
"name": "CtsBroadcastTestCases",
"options": [
{ "exclude-annotation": "androidx.test.filters.LargeTest" },
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index f61737e..31f5e45 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -61,7 +61,6 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
-import android.app.Dialog;
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
@@ -555,12 +554,6 @@
// This user is already stopping, doesn't count.
continue;
}
- if (userId == UserHandle.USER_SYSTEM) {
- // We only count system user as running when it is not a pure system user.
- if (UserInfo.isSystemOnly(userId)) {
- continue;
- }
- }
runningUsers.add(userId);
}
return runningUsers;
@@ -1464,8 +1457,7 @@
}
private boolean shouldStartWithParent(UserInfo user) {
- final UserProperties properties = mInjector.getUserManagerInternal()
- .getUserProperties(user.id);
+ final UserProperties properties = getUserProperties(user.id);
return (properties != null && properties.getStartWithParent())
&& !user.isQuietModeEnabled();
}
@@ -1683,6 +1675,7 @@
R.anim.screen_user_exit, R.anim.screen_user_enter);
t.traceEnd();
}
+ dismissUserSwitchDialog(); // so that we don't hold a reference to mUserSwitchingDialog
boolean needStart = false;
boolean updateUmState = false;
@@ -1869,6 +1862,8 @@
boolean success = startUser(targetUserId, USER_START_MODE_FOREGROUND);
if (!success) {
mInjector.getWindowManager().setSwitchingUser(false);
+ mTargetUserId = UserHandle.USER_NULL;
+ dismissUserSwitchDialog();
}
}
@@ -2017,6 +2012,10 @@
return true;
}
+ private void dismissUserSwitchDialog() {
+ mInjector.dismissUserSwitchingDialog();
+ }
+
private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
// The dialog will show and then initiate the user switch by calling startUserInForeground
mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
@@ -2773,6 +2772,10 @@
return mInjector.getUserManager().getUserInfo(userId);
}
+ private @Nullable UserProperties getUserProperties(@UserIdInt int userId) {
+ return mInjector.getUserManagerInternal().getUserProperties(userId);
+ }
+
int[] getUserIds() {
return mInjector.getUserManager().getUserIds();
}
@@ -2903,7 +2906,8 @@
if (getStartedUserState(userId) == null) {
return false;
}
- if (!mInjector.getUserManager().isCredentialSharableWithParent(userId)) {
+ final UserProperties properties = getUserProperties(userId);
+ if (properties == null || !properties.getIsCredentialSharableWithParent()) {
return false;
}
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
@@ -3468,6 +3472,9 @@
private UserManagerService mUserManager;
private UserManagerInternal mUserManagerInternal;
private Handler mHandler;
+ private final Object mUserSwitchingDialogLock = new Object();
+ @GuardedBy("mUserSwitchingDialogLock")
+ private UserSwitchingDialog mUserSwitchingDialog;
Injector(ActivityManagerService service) {
mService = service;
@@ -3643,6 +3650,15 @@
mService.mCpHelper.installEncryptionUnawareProviders(userId);
}
+ void dismissUserSwitchingDialog() {
+ synchronized (mUserSwitchingDialogLock) {
+ if (mUserSwitchingDialog != null) {
+ mUserSwitchingDialog.dismiss();
+ mUserSwitchingDialog = null;
+ }
+ }
+ }
+
void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
if (mService.mContext.getPackageManager()
@@ -3653,10 +3669,13 @@
Slogf.w(TAG, "Showing user switch dialog on UserController, it could cause a race "
+ "condition if it's shown by CarSystemUI as well");
}
- final Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser,
- toUser, true /* above system */, switchingFromSystemUserMessage,
- switchingToSystemUserMessage);
- d.show();
+ synchronized (mUserSwitchingDialogLock) {
+ dismissUserSwitchingDialog();
+ mUserSwitchingDialog = new UserSwitchingDialog(mService, mService.mContext,
+ fromUser, toUser, true /* above system */, switchingFromSystemUserMessage,
+ switchingToSystemUserMessage);
+ mUserSwitchingDialog.show();
+ }
}
void reportGlobalUsageEvent(int event) {
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 7a6603d..a5651bf 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -95,9 +95,7 @@
R.layout.user_switching_dialog, null);
String viewMessage = null;
- if (UserManager.isSplitSystemUser() && mNewUser.id == UserHandle.USER_SYSTEM) {
- viewMessage = res.getString(R.string.user_logging_out_message, mOldUser.name);
- } else if (UserManager.isDeviceInDemoMode(mContext)) {
+ if (UserManager.isDeviceInDemoMode(mContext)) {
if (mOldUser.isDemo()) {
viewMessage = res.getString(R.string.demo_restarting_message);
} else {
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 908cb3f..684d6a0 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -17,6 +17,8 @@
package com.android.server.app;
import static android.Manifest.permission.MANAGE_GAME_ACTIVITY;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -34,7 +36,6 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.Uri;
-import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.games.CreateGameSessionRequest;
@@ -51,7 +52,6 @@
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.view.WindowManager;
import android.window.ScreenCapture;
import com.android.internal.annotations.GuardedBy;
@@ -61,6 +61,7 @@
import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
@@ -863,8 +864,6 @@
Slog.w(TAG, "Could not get bitmap for id: " + taskId);
callback.complete(GameScreenshotResult.createInternalErrorResult());
} else {
- final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(
- bitmap);
final RunningTaskInfo runningTaskInfo =
mGameTaskInfoProvider.getRunningTaskInfo(taskId);
if (runningTaskInfo == null) {
@@ -879,11 +878,17 @@
callback.complete(GameScreenshotResult.createSuccessResult());
}
};
- mScreenshotHelper.provideScreenshot(bundle, crop, Insets.NONE, taskId,
- mUserHandle.getIdentifier(), gameSessionRecord.getComponentName(),
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
- BackgroundThread.getHandler(),
- completionConsumer);
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(gameSessionRecord.getComponentName())
+ .setTaskId(taskId)
+ .setUserId(mUserHandle.getIdentifier())
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(crop)
+ .setInsets(Insets.NONE)
+ .build();
+ mScreenshotHelper.takeScreenshot(
+ request, BackgroundThread.getHandler(), completionConsumer);
}
});
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9877ed3..21bd7bc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1718,8 +1718,9 @@
mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS);
} catch (Exception e) {
Log.e(TAG, "Exception acquiring wakelock", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- Binder.restoreCallingIdentity(identity);
}
if (MESSAGES_MUTE_MUSIC.contains(msg)) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cb98c66..92a9f46 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -908,9 +908,6 @@
private final SoundDoseHelper mSoundDoseHelper;
- @GuardedBy("mSettingsLock")
- private int mCurrentImeUid;
-
private final Object mSupportedSystemUsagesLock = new Object();
@GuardedBy("mSupportedSystemUsagesLock")
private @AttributeSystemUsage int[] mSupportedSystemUsages =
@@ -2856,10 +2853,14 @@
super.getPreferredDevicesForStrategy_enforcePermission();
List<AudioDeviceAttributes> devices = new ArrayList<>();
+ int status = AudioSystem.SUCCESS;
final long identity = Binder.clearCallingIdentity();
- final int status = AudioSystem.getDevicesForRoleAndStrategy(
- strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
- Binder.restoreCallingIdentity(identity);
+ try {
+ status = AudioSystem.getDevicesForRoleAndStrategy(
+ strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
if (status != AudioSystem.SUCCESS) {
Log.e(TAG, String.format("Error %d in getPreferredDeviceForStrategy(%d)",
status, strategy));
@@ -3045,10 +3046,14 @@
super.getPreferredDevicesForCapturePreset_enforcePermission();
List<AudioDeviceAttributes> devices = new ArrayList<>();
+ int status = AudioSystem.SUCCESS;
final long identity = Binder.clearCallingIdentity();
- final int status = AudioSystem.getDevicesForRoleAndCapturePreset(
- capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
- Binder.restoreCallingIdentity(identity);
+ try {
+ status = AudioSystem.getDevicesForRoleAndCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
if (status != AudioSystem.SUCCESS) {
Log.e(TAG, String.format("Error %d in getPreferredDeviceForCapturePreset(%d)",
status, capturePreset));
@@ -3653,11 +3658,14 @@
}
final long identity = Binder.clearCallingIdentity();
- mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
- isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC),
- getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
- isStreamMute(AudioSystem.STREAM_MUSIC));
- Binder.restoreCallingIdentity(identity);
+ try {
+ mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
+ isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC),
+ getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
+ isStreamMute(AudioSystem.STREAM_MUSIC));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
private int getNewRingerMode(int stream, int index, int flags) {
@@ -4985,26 +4993,26 @@
if (getCurrentUserId() == userId || userId == android.os.Process.SYSTEM_UID) {
final boolean currentMute = mAudioSystem.isMicrophoneMuted();
final long identity = Binder.clearCallingIdentity();
- final int ret = mAudioSystem.muteMicrophone(muted);
-
- // update cache with the real state independently from what was set
- mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted();
- if (ret != AudioSystem.AUDIO_STATUS_OK) {
- Log.e(TAG, "Error changing mic mute state to " + muted + " current:"
- + mMicMuteFromSystemCached);
- }
-
- new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
- .setUid(userId)
- .set(MediaMetrics.Property.EVENT, "setMicrophoneMuteNoCallerCheck")
- .set(MediaMetrics.Property.MUTE, mMicMuteFromSystemCached
- ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
- .set(MediaMetrics.Property.REQUEST, muted
- ? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE)
- .set(MediaMetrics.Property.STATUS, ret)
- .record();
-
try {
+ final int ret = mAudioSystem.muteMicrophone(muted);
+
+ // update cache with the real state independently from what was set
+ mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted();
+ if (ret != AudioSystem.AUDIO_STATUS_OK) {
+ Log.e(TAG, "Error changing mic mute state to " + muted + " current:"
+ + mMicMuteFromSystemCached);
+ }
+
+ new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
+ .setUid(userId)
+ .set(MediaMetrics.Property.EVENT, "setMicrophoneMuteNoCallerCheck")
+ .set(MediaMetrics.Property.MUTE, mMicMuteFromSystemCached
+ ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
+ .set(MediaMetrics.Property.REQUEST, muted
+ ? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE)
+ .set(MediaMetrics.Property.STATUS, ret)
+ .record();
+
// send the intent even if there was a failure to change the actual mute state:
// the AudioManager.setMicrophoneMute API doesn't have a return value to
// indicate if the call failed to successfully change the mute state, and receiving
@@ -5614,9 +5622,13 @@
+ mMode.get() + " requested mode: " + requestedMode);
}
if (mode != mMode.get() || force) {
+ int status = AudioSystem.SUCCESS;
final long identity = Binder.clearCallingIdentity();
- int status = mAudioSystem.setPhoneState(mode, uid);
- Binder.restoreCallingIdentity(identity);
+ try {
+ status = mAudioSystem.setPhoneState(mode, uid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
if (status == AudioSystem.AUDIO_STATUS_OK) {
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode: mode successfully set to " + mode);
@@ -6023,17 +6035,22 @@
}
final long ident = Binder.clearCallingIdentity();
- boolean status =
- mDeviceBroker.setCommunicationDevice(cb, pid, device, eventSource);
- Binder.restoreCallingIdentity(ident);
- return status;
+ try {
+ return mDeviceBroker.setCommunicationDevice(cb, pid, device, eventSource);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
/** @see AudioManager#getCommunicationDevice() */
public int getCommunicationDevice() {
+ AudioDeviceInfo device = null;
final long ident = Binder.clearCallingIdentity();
- AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice();
- Binder.restoreCallingIdentity(ident);
+ try {
+ device = mDeviceBroker.getCommunicationDevice();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
if (device == null) {
return 0;
}
@@ -6083,8 +6100,11 @@
? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
.record();
final long ident = Binder.clearCallingIdentity();
- mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
- Binder.restoreCallingIdentity(ident);
+ try {
+ mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
/** @see AudioManager#isSpeakerphoneOn() */
@@ -6224,8 +6244,11 @@
return;
}
final long ident = Binder.clearCallingIdentity();
- mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
- Binder.restoreCallingIdentity(ident);
+ try {
+ mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
mmi.record();
}
@@ -6241,8 +6264,11 @@
.append(") from u/pid:").append(uid).append("/")
.append(pid).toString();
final long ident = Binder.clearCallingIdentity();
- mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
- Binder.restoreCallingIdentity(ident);
+ try {
+ mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
.setUid(uid)
.setPid(pid)
@@ -6531,8 +6557,11 @@
(TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
final long ident = Binder.clearCallingIdentity();
- IsInCall = telecomManager.isInCall();
- Binder.restoreCallingIdentity(ident);
+ try {
+ IsInCall = telecomManager.isInCall();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
int mode = mMode.get();
return (IsInCall
@@ -6672,10 +6701,13 @@
private void queueMsgUnderWakeLock(Handler handler, int msg,
int arg1, int arg2, Object obj, int delay) {
final long ident = Binder.clearCallingIdentity();
- // Always acquire the wake lock as AudioService because it is released by the
- // message handler.
- mAudioEventWakeLock.acquire();
- Binder.restoreCallingIdentity(ident);
+ try {
+ // Always acquire the wake lock as AudioService because it is released by the
+ // message handler.
+ mAudioEventWakeLock.acquire();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
sendMsg(handler, msg, SENDMSG_QUEUE, arg1, arg2, obj, delay);
}
@@ -10075,6 +10107,55 @@
"com.android.server.audio", "AudioService");
}
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public float getRs2Value() {
+ super.getRs2Value_enforcePermission();
+ return mSoundDoseHelper.getRs2Value();
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public void setRs2Value(float rs2Value) {
+ super.setRs2Value_enforcePermission();
+ mSoundDoseHelper.setRs2Value(rs2Value);
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public float getCsd() {
+ super.getCsd_enforcePermission();
+ return mSoundDoseHelper.getCsd();
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public void setCsd(float csd) {
+ super.setCsd_enforcePermission();
+ mSoundDoseHelper.setCsd(csd);
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public void forceUseFrameworkMel(boolean useFrameworkMel) {
+ super.forceUseFrameworkMel_enforcePermission();
+ mSoundDoseHelper.forceUseFrameworkMel(useFrameworkMel);
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
+ super.forceComputeCsdOnAllDevices_enforcePermission();
+ mSoundDoseHelper.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ public boolean isCsdEnabled() {
+ super.isCsdEnabled_enforcePermission();
+ return mSoundDoseHelper.isCsdEnabled();
+ }
+
//==========================================================================================
// Hdmi CEC:
// - System audio mode:
@@ -10426,7 +10507,6 @@
+ " FromRestrictions=" + mMicMuteFromRestrictions
+ " FromApi=" + mMicMuteFromApi
+ " from system=" + mMicMuteFromSystemCached);
- pw.print(" mCurrentImeUid="); pw.println(mCurrentImeUid);
dumpAccessibilityServiceUids(pw);
dumpAssistantServicesUids(pw);
@@ -10561,6 +10641,15 @@
}
@Override
+ @Nullable
+ public IVolumeController getVolumeController() {
+ enforceVolumeController("get the volume controller");
+ if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
+
+ return mVolumeController.getController();
+ }
+
+ @Override
public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) {
enforceVolumeController("notify about volume controller visibility");
@@ -10605,6 +10694,10 @@
mVisible = false;
}
+ public IVolumeController getController() {
+ return mController;
+ }
+
public void loadSettings(ContentResolver cr) {
mLongPressTimeout = mSettings.getSecureIntForUser(cr,
Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT);
@@ -11694,13 +11787,16 @@
int callingUid = Binder.getCallingUid();
int flags = AudioAttributes.capturePolicyToFlags(capturePolicy, 0x0);
final long identity = Binder.clearCallingIdentity();
- synchronized (mPlaybackMonitor) {
- int result = mAudioSystem.setAllowedCapturePolicy(callingUid, flags);
- if (result == AudioSystem.AUDIO_STATUS_OK) {
- mPlaybackMonitor.setAllowedCapturePolicy(callingUid, capturePolicy);
+ try {
+ synchronized (mPlaybackMonitor) {
+ int result = mAudioSystem.setAllowedCapturePolicy(callingUid, flags);
+ if (result == AudioSystem.AUDIO_STATUS_OK) {
+ mPlaybackMonitor.setAllowedCapturePolicy(callingUid, capturePolicy);
+ }
+ return result;
}
+ } finally {
Binder.restoreCallingIdentity(identity);
- return result;
}
}
@@ -11711,9 +11807,11 @@
public int getAllowedCapturePolicy() {
int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
- int capturePolicy = mPlaybackMonitor.getAllowedCapturePolicy(callingUid);
- Binder.restoreCallingIdentity(identity);
- return capturePolicy;
+ try {
+ return mPlaybackMonitor.getAllowedCapturePolicy(callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
//======================
@@ -11855,8 +11953,11 @@
}
}
final long identity = Binder.clearCallingIdentity();
- mAudioSystem.registerPolicyMixes(mMixes, false);
- Binder.restoreCallingIdentity(identity);
+ try {
+ mAudioSystem.registerPolicyMixes(mMixes, false);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
synchronized (mAudioPolicies) {
mAudioPolicies.remove(mPolicyCallback.asBinder());
}
@@ -11915,9 +12016,12 @@
@AudioSystem.AudioSystemError int connectMixes() {
final long identity = Binder.clearCallingIdentity();
- int status = mAudioSystem.registerPolicyMixes(mMixes, true);
- Binder.restoreCallingIdentity(identity);
- return status;
+ try {
+ return mAudioSystem.registerPolicyMixes(mMixes, true);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
}
int setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 919b850..bc61b37 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -53,6 +53,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -70,9 +71,6 @@
/*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
"com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
- /** Flag to enable/disable the sound dose computation. */
- private static final boolean USE_CSD_FOR_SAFE_HEARING = false;
-
// mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
// It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
// or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
@@ -96,8 +94,6 @@
private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
- private static final float CUSTOM_RS2_VALUE = 90;
-
// timeouts for the CSD warnings, -1 means no timeout (dialog must be ack'd by user)
private static final int CSD_WARNING_TIMEOUT_MS_DOSE_1X = 7000;
private static final int CSD_WARNING_TIMEOUT_MS_DOSE_5X = 5000;
@@ -233,6 +229,94 @@
initCsd();
}
+ float getRs2Value() {
+ if (!mEnableCsd) {
+ return 0.f;
+ }
+
+ Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+ try {
+ return mSoundDose.getOutputRs2();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while getting the RS2 exposure value", e);
+ return 0.f;
+ }
+ }
+
+ void setRs2Value(float rs2Value) {
+ if (!mEnableCsd) {
+ return;
+ }
+
+ Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+ try {
+ mSoundDose.setOutputRs2(rs2Value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while setting the RS2 exposure value", e);
+ }
+ }
+
+ float getCsd() {
+ if (!mEnableCsd) {
+ return -1.f;
+ }
+
+ Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+ try {
+ return mSoundDose.getCsd();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while getting the CSD value", e);
+ return -1.f;
+ }
+ }
+
+ void setCsd(float csd) {
+ if (!mEnableCsd) {
+ return;
+ }
+
+ Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+ try {
+ final SoundDoseRecord record = new SoundDoseRecord();
+ record.timestamp = System.currentTimeMillis();
+ record.value = csd;
+ final SoundDoseRecord[] recordArray = new SoundDoseRecord[] { record };
+ mSoundDose.resetCsd(csd, recordArray);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while setting the CSD value", e);
+ }
+ }
+
+ void forceUseFrameworkMel(boolean useFrameworkMel) {
+ if (!mEnableCsd) {
+ return;
+ }
+
+ Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+ try {
+ mSoundDose.forceUseFrameworkMel(useFrameworkMel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+ }
+ }
+
+ void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
+ if (!mEnableCsd) {
+ return;
+ }
+
+ Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+ try {
+ mSoundDose.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while forcing CSD computation on all devices", e);
+ }
+ }
+
+ boolean isCsdEnabled() {
+ return mEnableCsd;
+ }
+
/*package*/ int safeMediaVolumeIndex(int device) {
if (!mSafeMediaVolumeDevices.contains(device)) {
return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -293,7 +377,7 @@
|| (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC)
|| (!mSafeMediaVolumeDevices.contains(device))
|| (index <= safeMediaVolumeIndex(device))
- || USE_CSD_FOR_SAFE_HEARING;
+ || mEnableCsd;
}
/*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
@@ -329,7 +413,7 @@
/*package*/ void scheduleMusicActiveCheck() {
synchronized (mSafeMediaVolumeStateLock) {
cancelMusicActiveCheck();
- if (!USE_CSD_FOR_SAFE_HEARING) {
+ if (!mEnableCsd) {
mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
REQUEST_CODE_CHECK_MUSIC_ACTIVE,
new Intent(ACTION_CHECK_MUSIC_ACTIVE),
@@ -460,14 +544,13 @@
private void initCsd() {
synchronized (mSafeMediaVolumeStateLock) {
- if (USE_CSD_FOR_SAFE_HEARING) {
+ if (mEnableCsd) {
Log.v(TAG, "Initializing sound dose");
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
try {
if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
- mSoundDose.setOutputRs2(CUSTOM_RS2_VALUE);
if (mCurrentCsd != 0.f) {
Log.d(TAG, "Resetting the saved sound dose value " + mCurrentCsd);
SoundDoseRecord[] records = mDoseRecords.toArray(
@@ -502,7 +585,7 @@
// The persisted state is either "disabled" or "active": this is the state applied
// next time we boot and cannot be "inactive"
int persistedState;
- if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !USE_CSD_FOR_SAFE_HEARING) {
+ if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !mEnableCsd) {
persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
// The state can already be "inactive" here if the user has forced it before
// the 30 seconds timeout for forced configuration. In this case we don't reset
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 2ac8b43..513b3e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -230,6 +230,10 @@
session.getSession().onPointerDown(pc.pointerId, (int) pc.x, (int) pc.y, pc.minor,
pc.major);
}
+
+ if (getListener() != null) {
+ getListener().onUdfpsPointerDown(getSensorId());
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer down", e);
}
@@ -246,6 +250,10 @@
} else {
session.getSession().onPointerUp(pc.pointerId);
}
+
+ if (getListener() != null) {
+ getListener().onUdfpsPointerUp(getSensorId());
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer up", e);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index 42fe9d8..a589313 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -64,10 +64,27 @@
*/
public static final int FLAG_EMULATED_ONLY = 1 << 2;
+ /**
+ * This flag indicates that the corresponding state should be automatically canceled when the
+ * requesting app is no longer on top. The app is considered not on top when (1) the top
+ * activity in the system is from a different app, (2) the device is in sleep mode, or
+ * (3) the keyguard shows up.
+ */
+ public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3;
+
+ /**
+ * This flag indicates that the corresponding state should be disabled when the device is
+ * overheating and reaching the critical status.
+ */
+ public static final int FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
+
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
FLAG_CANCEL_OVERRIDE_REQUESTS,
- FLAG_APP_INACCESSIBLE
+ FLAG_APP_INACCESSIBLE,
+ FLAG_EMULATED_ONLY,
+ FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+ FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceStateFlags {}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index c856cab..064cd2d 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -33,6 +33,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.TaskStackListener;
import android.content.Context;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
@@ -176,6 +177,12 @@
@NonNull
private final SystemPropertySetter mSystemPropertySetter;
+ @VisibleForTesting
+ TaskStackListener mOverrideRequestTaskStackListener = new OverrideRequestTaskStackListener();
+ @VisibleForTesting
+ ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver =
+ new OverrideRequestScreenObserver();
+
public DeviceStateManagerService(@NonNull Context context) {
this(context, DeviceStatePolicy.Provider
.fromResources(context.getResources())
@@ -215,6 +222,9 @@
readStatesAvailableForRequestFromApps();
mFoldedDeviceStates = readFoldedStates();
}
+
+ mActivityTaskManagerInternal.registerTaskStackListener(mOverrideRequestTaskStackListener);
+ mActivityTaskManagerInternal.registerScreenObserver(mOverrideRequestScreenObserver);
}
@VisibleForTesting
@@ -842,9 +852,7 @@
* @param state state that is being requested.
*/
private void assertCanRequestDeviceState(int callingPid, int state) {
- final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
- if (topApp == null || topApp.getPid() != callingPid
- || !isStateAvailableForAppRequests(state)) {
+ if (!isTopApp(callingPid) || !isStateAvailableForAppRequests(state)) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top app "
@@ -859,14 +867,18 @@
* @param callingPid Process ID that is requesting this state change
*/
private void assertCanControlDeviceState(int callingPid) {
- final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
- if (topApp == null || topApp.getPid() != callingPid) {
+ if (!isTopApp(callingPid)) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top app.");
}
}
+ private boolean isTopApp(int callingPid) {
+ final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+ return topApp != null && topApp.getPid() == callingPid;
+ }
+
private boolean isStateAvailableForAppRequests(int state) {
synchronized (mLock) {
return mDeviceStatesAvailableForAppRequests.contains(state);
@@ -1185,4 +1197,52 @@
}
}
}
+
+ @GuardedBy("mLock")
+ private boolean shouldCancelOverrideRequestWhenRequesterNotOnTop() {
+ if (mActiveOverride.isEmpty()) {
+ return false;
+ }
+ int identifier = mActiveOverride.get().getRequestedState();
+ DeviceState deviceState = mDeviceStates.get(identifier);
+ return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
+ }
+
+ private class OverrideRequestTaskStackListener extends TaskStackListener {
+ @Override
+ public void onTaskStackChanged() throws RemoteException {
+ synchronized (mLock) {
+ if (!shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+ return;
+ }
+
+ OverrideRequest request = mActiveOverride.get();
+ if (!isTopApp(request.getPid())) {
+ mOverrideRequestController.cancelRequest(request);
+ }
+ }
+ }
+ }
+
+ private class OverrideRequestScreenObserver implements
+ ActivityTaskManagerInternal.ScreenObserver {
+
+ @Override
+ public void onAwakeStateChanged(boolean isAwake) {
+ synchronized (mLock) {
+ if (!isAwake && shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+ mOverrideRequestController.cancelRequest(mActiveOverride.get());
+ }
+ }
+ }
+
+ @Override
+ public void onKeyguardStateChanged(boolean isShowing) {
+ synchronized (mLock) {
+ if (isShowing && shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+ mOverrideRequestController.cancelRequest(mActiveOverride.get());
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 7448611..4a9b562 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -96,7 +96,11 @@
mListeners.remove(l);
}
- void setBrightness(float brightness) {
+ /**
+ * Sets the brigtness and broadcasts the change to the listeners.
+ * @param brightness The value to which the brightness is to be set.
+ */
+ public void setBrightness(float brightness) {
if (Float.isNaN(brightness)) {
Slog.w(TAG, "Attempting to set invalid brightness");
return;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 06b99f8..d558e69 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -109,7 +109,6 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.IntArray;
@@ -261,13 +260,6 @@
final SparseArray<Pair<IVirtualDevice, DisplayWindowPolicyController>>
mDisplayWindowPolicyControllers = new SparseArray<>();
- /**
- * Map of every internal primary display device {@link HighBrightnessModeMetadata}s indexed by
- * {@link DisplayDevice#mUniqueId}.
- */
- public final ArrayMap<String, HighBrightnessModeMetadata> mHighBrightnessModeMetadataMap =
- new ArrayMap<>();
-
// List of all currently registered display adapters.
private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
@@ -1648,16 +1640,7 @@
DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
- final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
- if (device == null) {
- Slog.wtf(TAG, "Display Device is null in DisplayManagerService for display: "
- + display.getDisplayIdLocked());
- return;
- }
-
- final String uniqueId = device.getUniqueId();
- HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMap.get(uniqueId);
- dpc.onDisplayChanged(hbmMetadata);
+ dpc.onDisplayChanged();
}
}
@@ -1715,15 +1698,7 @@
final int displayId = display.getDisplayIdLocked();
final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
- final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
- if (device == null) {
- Slog.wtf(TAG, "Display Device is null in DisplayManagerService for display: "
- + display.getDisplayIdLocked());
- return;
- }
- final String uniqueId = device.getUniqueId();
- HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMap.get(uniqueId);
- dpc.onDisplayChanged(hbmMetadata);
+ dpc.onDisplayChanged();
}
}
@@ -2676,31 +2651,6 @@
mLogicalDisplayMapper.forEachLocked(this::addDisplayPowerControllerLocked);
}
- private HighBrightnessModeMetadata getHighBrightnessModeMetadata(LogicalDisplay display) {
- final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
- if (device == null) {
- Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: "
- + display.getDisplayIdLocked());
- return null;
- }
-
- // HBM brightness mode is only applicable to internal physical displays.
- if (display.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
- return null;
- }
-
- final String uniqueId = device.getUniqueId();
-
- if (mHighBrightnessModeMetadataMap.containsKey(uniqueId)) {
- return mHighBrightnessModeMetadataMap.get(uniqueId);
- }
-
- // HBM Time info not present. Create a new one for this physical display.
- HighBrightnessModeMetadata hbmInfo = new HighBrightnessModeMetadata();
- mHighBrightnessModeMetadataMap.put(uniqueId, hbmInfo);
- return hbmInfo;
- }
-
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
private void addDisplayPowerControllerLocked(LogicalDisplay display) {
if (mPowerHandler == null) {
@@ -2716,23 +2666,17 @@
display, mSyncRoot);
final DisplayPowerControllerInterface displayPowerController;
- // If display is internal and has a HighBrightnessModeMetadata mapping, use that.
- // Or create a new one and use that.
- // We also need to pass a mapping of the HighBrightnessModeTimeInfoMap to
- // displayPowerController, so the hbm info can be correctly associated
- // with the corresponding displaydevice.
- HighBrightnessModeMetadata hbmMetadata = getHighBrightnessModeMetadata(display);
if (DeviceConfig.getBoolean("display_manager",
"use_newly_structured_display_power_controller", true)) {
displayPowerController = new DisplayPowerController2(
mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display), hbmMetadata);
+ () -> handleBrightnessChange(display));
} else {
displayPowerController = new DisplayPowerController(
mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display), hbmMetadata);
+ () -> handleBrightnessChange(display));
}
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 142ec68..cdaa3d0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -388,7 +388,6 @@
private float[] mNitsRange;
private final HighBrightnessModeController mHbmController;
- private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
private final BrightnessThrottler mBrightnessThrottler;
@@ -506,14 +505,13 @@
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
- Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) {
+ Runnable onBrightnessChangeRunnable) {
mInjector = injector != null ? injector : new Injector();
mClock = mInjector.getClock();
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
mTag = "DisplayPowerController[" + mDisplayId + "]";
- mHighBrightnessModeMetadata = hbmMetadata;
mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId);
@@ -792,7 +790,7 @@
* Make sure DisplayManagerService.mSyncRoot is held when this is called
*/
@Override
- public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata) {
+ public void onDisplayChanged() {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
Slog.wtf(mTag, "Display Device is null in DisplayPowerController for display: "
@@ -814,7 +812,7 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
- loadFromDisplayDeviceConfig(token, info, hbmMetadata);
+ loadFromDisplayDeviceConfig(token, info);
/// Since the underlying display-device changed, we really don't know the
// last command that was sent to change it's state. Lets assume it is unknown so
@@ -866,8 +864,7 @@
}
}
- private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
- HighBrightnessModeMetadata hbmMetadata) {
+ private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -880,7 +877,6 @@
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
- mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
mDisplayDeviceConfig.getHighBrightnessModeData(),
new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
@@ -1965,7 +1961,7 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.update();
}
- }, mHighBrightnessModeMetadata, mContext);
+ }, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 96342f3..df0e2d3 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -68,6 +68,7 @@
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
@@ -197,8 +198,6 @@
// mScreenBrightnessDimConfig.
private final float mScreenBrightnessMinimumDimAmount;
- private final float mScreenBrightnessDefault;
-
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
@@ -328,12 +327,9 @@
private float[] mNitsRange;
private final HighBrightnessModeController mHbmController;
- private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
private final BrightnessThrottler mBrightnessThrottler;
- private final BrightnessSetting mBrightnessSetting;
-
private final Runnable mOnBrightnessChangeRunnable;
private final BrightnessEvent mLastBrightnessEvent;
@@ -384,19 +380,6 @@
@Nullable
private BrightnessConfiguration mBrightnessConfiguration;
- // The last brightness that was set by the user and not temporary. Set to
- // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
- private float mLastUserSetScreenBrightness = Float.NaN;
-
- // The screen brightness setting has changed but not taken effect yet. If this is different
- // from the current screen brightness setting then this is coming from something other than us
- // and should be considered a user interaction.
- private float mPendingScreenBrightnessSetting;
-
- // The last observed screen brightness setting, either set by us or by the settings app on
- // behalf of the user.
- private float mCurrentScreenBrightnessSetting;
-
// The last auto brightness adjustment that was set by the user and not temporary. Set to
// Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
private float mAutoBrightnessAdjustment;
@@ -417,7 +400,6 @@
private ObjectAnimator mColorFadeOnAnimator;
private ObjectAnimator mColorFadeOffAnimator;
private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
- private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
// True if this DisplayPowerController2 has been stopped and should no longer be running.
private boolean mStopped;
@@ -433,7 +415,7 @@
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
- Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) {
+ Runnable onBrightnessChangeRunnable) {
mInjector = injector != null ? injector : new Injector();
mClock = mInjector.getClock();
@@ -449,7 +431,6 @@
mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
() -> updatePowerState(), mDisplayId, mSensorManager);
- mHighBrightnessModeMetadata = hbmMetadata;
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
@@ -471,8 +452,6 @@
mBlanker = blanker;
mContext = context;
mBrightnessTracker = brightnessTracker;
- // TODO: b/186428377 update brightness setting when display changes
- mBrightnessSetting = brightnessSetting;
mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
PowerManager pm = context.getSystemService(PowerManager.class);
@@ -480,18 +459,13 @@
final Resources resources = context.getResources();
// DOZE AND DIM SETTINGS
- mScreenBrightnessDozeConfig = clampAbsoluteBrightness(
+ mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
- mScreenBrightnessDimConfig = clampAbsoluteBrightness(
+ mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
mScreenBrightnessMinimumDimAmount = resources.getFloat(
R.dimen.config_screenBrightnessMinimumDimAmountFloat);
-
- // NORMAL SCREEN SETTINGS
- mScreenBrightnessDefault = clampAbsoluteBrightness(
- mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
-
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
@@ -499,7 +473,10 @@
mHbmController = createHbmControllerLocked();
mBrightnessThrottler = createBrightnessThrottlerLocked();
-
+ mDisplayBrightnessController =
+ new DisplayBrightnessController(context, null,
+ mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
+ brightnessSetting, () -> postBrightnessChangeRunnable());
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
@@ -554,12 +531,7 @@
mBrightnessBucketsInDozeConfig = resources.getBoolean(
R.bool.config_displayBrightnessBucketsInDoze);
-
- mDisplayBrightnessController =
- new DisplayBrightnessController(context, null, mDisplayId);
- mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
- mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -709,7 +681,7 @@
* Make sure DisplayManagerService.mSyncRoot lock is held when this is called
*/
@Override
- public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata) {
+ public void onDisplayChanged() {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
@@ -723,7 +695,6 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
-
mHandler.post(() -> {
boolean changed = false;
if (mDisplayDevice != device) {
@@ -732,7 +703,7 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
- loadFromDisplayDeviceConfig(token, info, hbmMetadata);
+ loadFromDisplayDeviceConfig(token, info);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
// Since the underlying display-device changed, we really don't know the
@@ -773,16 +744,13 @@
mAutomaticBrightnessController.stop();
}
- if (mBrightnessSetting != null) {
- mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
- }
+ mDisplayBrightnessController.stop();
mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
}
}
- private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
- HighBrightnessModeMetadata hbmMetadata) {
+ private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -794,7 +762,6 @@
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
- mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
mDisplayDeviceConfig.getHighBrightnessModeData(),
new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
@@ -854,12 +821,14 @@
if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
}
- mBrightnessSettingListener = brightnessValue -> {
+
+ BrightnessSetting.BrightnessSettingListener brightnessSettingListener = brightnessValue -> {
Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
mHandler.sendMessage(msg);
};
+ mDisplayBrightnessController
+ .registerBrightnessSettingChangeListener(brightnessSettingListener);
- mBrightnessSetting.registerListener(mBrightnessSettingListener);
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
@@ -1209,7 +1178,8 @@
? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
- final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
+ final boolean userSetBrightnessChanged = mDisplayBrightnessController
+ .updateUserSetScreenBrightness();
final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
@@ -1235,7 +1205,7 @@
hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
mAutomaticBrightnessController.configure(autoBrightnessState,
mBrightnessConfiguration,
- mLastUserSetScreenBrightness,
+ mDisplayBrightnessController.getLastUserSetScreenBrightness(),
userSetBrightnessChanged, autoBrightnessAdjustment,
autoBrightnessAdjustmentChanged, mPowerRequest.policy,
mShouldResetShortTermModel);
@@ -1247,7 +1217,7 @@
}
boolean updateScreenBrightnessSetting = false;
-
+ float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
boolean slowChange = false;
if (Float.isNaN(brightnessState)) {
@@ -1258,14 +1228,14 @@
newAutoBrightnessAdjustment =
mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
}
- if (isValidBrightnessValue(brightnessState)
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)
|| brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
// Use current auto-brightness value and slowly adjust to changes.
brightnessState = clampScreenBrightness(brightnessState);
if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
slowChange = true; // slowly adapt to auto-brightness
}
- updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
mAppliedAutoBrightness = true;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
if (mScreenOffBrightnessSensorController != null) {
@@ -1303,9 +1273,10 @@
if (Float.isNaN(brightnessState) && autoBrightnessEnabled
&& mScreenOffBrightnessSensorController != null) {
brightnessState = mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
- if (isValidBrightnessValue(brightnessState)) {
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)) {
brightnessState = clampScreenBrightness(brightnessState);
- updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
+ updateScreenBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness()
+ != brightnessState;
mBrightnessReasonTemp.setReason(
BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
}
@@ -1313,8 +1284,8 @@
// Apply manual brightness.
if (Float.isNaN(brightnessState)) {
- brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting);
- if (brightnessState != mCurrentScreenBrightnessSetting) {
+ brightnessState = clampScreenBrightness(currentBrightnessSetting);
+ if (brightnessState != currentBrightnessSetting) {
// The manually chosen screen brightness is outside of the currently allowed
// range (i.e., high-brightness-mode), make sure we tell the rest of the system
// by updating the setting.
@@ -1351,7 +1322,7 @@
// before applying the low power or dim transformations so that the slider
// accurately represents the full possible range, even if they range changes what
// it means in absolute terms.
- updateScreenBrightnessSetting(brightnessState);
+ mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessState);
}
// Apply dimming by at least some minimum amount when user activity
@@ -1462,7 +1433,7 @@
final float currentBrightness = mPowerState.getScreenBrightness();
final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
- if (isValidBrightnessValue(animateValue)
+ if (BrightnessUtils.isValidBrightnessValue(animateValue)
&& (animateValue != currentBrightness
|| sdrAnimateValue != currentSdrBrightness)) {
if (initialRampSkip || hasBrightnessBuckets
@@ -1747,7 +1718,7 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.update();
}
- }, mHighBrightnessModeMetadata, mContext);
+ }, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -1897,12 +1868,6 @@
mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
}
- // Checks whether the brightness is within the valid brightness range, not including off.
- private boolean isValidBrightnessValue(float brightness) {
- return brightness >= PowerManager.BRIGHTNESS_MIN
- && brightness <= PowerManager.BRIGHTNESS_MAX;
- }
-
private void animateScreenBrightness(float target, float sdrTarget, float rate) {
if (DEBUG) {
Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
@@ -2083,11 +2048,14 @@
}
private void handleSettingsChange(boolean userSwitch) {
- mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
+ mDisplayBrightnessController
+ .setPendingScreenBrightness(mDisplayBrightnessController
+ .getScreenBrightnessSetting());
mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
if (userSwitch) {
// Don't treat user switches as user initiated change.
- setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+ mDisplayBrightnessController.setCurrentScreenBrightness(mDisplayBrightnessController
+ .getPendingScreenBrightness());
updateAutoBrightnessAdjustment();
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.resetShortTermModel();
@@ -2116,34 +2084,12 @@
@Override
public float getScreenBrightnessSetting() {
- float brightness = mBrightnessSetting.getBrightness();
- if (Float.isNaN(brightness)) {
- brightness = mScreenBrightnessDefault;
- }
- return clampAbsoluteBrightness(brightness);
+ return mDisplayBrightnessController.getScreenBrightnessSetting();
}
@Override
public void setBrightness(float brightnessValue) {
- // Update the setting, which will eventually call back into DPC to have us actually update
- // the display with the new value.
- mBrightnessSetting.setBrightness(brightnessValue);
- }
-
- private void updateScreenBrightnessSetting(float brightnessValue) {
- if (!isValidBrightnessValue(brightnessValue)
- || brightnessValue == mCurrentScreenBrightnessSetting) {
- return;
- }
- setCurrentScreenBrightness(brightnessValue);
- mBrightnessSetting.setBrightness(brightnessValue);
- }
-
- private void setCurrentScreenBrightness(float brightnessValue) {
- if (brightnessValue != mCurrentScreenBrightnessSetting) {
- mCurrentScreenBrightnessSetting = brightnessValue;
- postBrightnessChangeRunnable();
- }
+ mDisplayBrightnessController.setBrightness(brightnessValue);
}
private void putAutoBrightnessAdjustmentSetting(float adjustment) {
@@ -2169,28 +2115,6 @@
return true;
}
- // We want to return true if the user has set the screen brightness.
- // RBC on, off, and intensity changes will return false.
- // Slider interactions whilst in RBC will return true, just as when in non-rbc.
- private boolean updateUserSetScreenBrightness() {
- if ((Float.isNaN(mPendingScreenBrightnessSetting)
- || mPendingScreenBrightnessSetting < 0.0f)) {
- return false;
- }
- if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
- mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mDisplayBrightnessController
- .setTemporaryBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- return false;
- }
- setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
- mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
- mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mDisplayBrightnessController
- .setTemporaryBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- return true;
- }
-
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean hadUserDataPoint) {
final float brightnessInNits = convertToNits(brightness);
@@ -2235,7 +2159,6 @@
pw.println();
pw.println("Display Power Controller Configuration:");
- pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
@@ -2266,9 +2189,6 @@
pw.println();
pw.println("Display Power Controller Thread State:");
pw.println(" mPowerRequest=" + mPowerRequest);
- pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
- pw.println(" mPendingScreenBrightnessSetting="
- + mPendingScreenBrightnessSetting);
pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
pw.println(" mBrightnessReason=" + mBrightnessReason);
pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
@@ -2385,11 +2305,6 @@
}
}
- private static float clampAbsoluteBrightness(float value) {
- return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX);
- }
-
private static float clampAutoBrightnessAdjustment(float value) {
return MathUtils.constrain(value, -1.0f, 1.0f);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 7b019846..e750ee2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -31,14 +31,10 @@
public interface DisplayPowerControllerInterface {
/**
- * Notified when the display is changed.
- * We use this to apply any changes that might be needed
+ * Notified when the display is changed. We use this to apply any changes that might be needed
* when displays get swapped on foldable devices.
- * We also pass the High brightness mode metadata like
- * remaining time and hbm events for the corresponding
- * physical display, to update the values correctly.
*/
- void onDisplayChanged(HighBrightnessModeMetadata hbmInfo);
+ void onDisplayChanged();
/**
* Unregisters all listeners and interrupts all running threads; halting future work.
diff --git a/services/core/java/com/android/server/display/HbmEvent.java b/services/core/java/com/android/server/display/HbmEvent.java
deleted file mode 100644
index 5675e2f..0000000
--- a/services/core/java/com/android/server/display/HbmEvent.java
+++ /dev/null
@@ -1,46 +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.server.display;
-
-
-/**
- * Represents an event in which High Brightness Mode was enabled.
- */
-class HbmEvent {
- private long mStartTimeMillis;
- private long mEndTimeMillis;
-
- HbmEvent(long startTimeMillis, long endTimeMillis) {
- this.mStartTimeMillis = startTimeMillis;
- this.mEndTimeMillis = endTimeMillis;
- }
-
- public long getStartTimeMillis() {
- return mStartTimeMillis;
- }
-
- public long getEndTimeMillis() {
- return mEndTimeMillis;
- }
-
- @Override
- public String toString() {
- return "HbmEvent: {startTimeMillis:" + mStartTimeMillis + ", endTimeMillis: "
- + mEndTimeMillis + "}, total: "
- + ((mEndTimeMillis - mStartTimeMillis) / 1000) + "]";
- }
-}
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 2c843a4..f98c7df 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -105,23 +105,30 @@
private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
/**
- * If HBM is currently running, this is the start time and set of all events,
- * for the current HBM session.
+ * If HBM is currently running, this is the start time for the current HBM session.
*/
- private HighBrightnessModeMetadata mHighBrightnessModeMetadata = null;
+ private long mRunningStartTimeMillis = -1;
+
+ /**
+ * Queue of previous HBM-events ordered from most recent to least recent.
+ * Meant to store only the events that fall into the most recent
+ * {@link HighBrightnessModeData#timeWindowMillis mHbmData.timeWindowMillis}.
+ */
+ private final ArrayDeque<HbmEvent> mEvents = new ArrayDeque<>();
+
HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
String displayUniqueId, float brightnessMin, float brightnessMax,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
- Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
+ Runnable hbmChangeCallback, Context context) {
this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
- brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context);
+ brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, context);
}
@VisibleForTesting
HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
- Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
+ Runnable hbmChangeCallback, Context context) {
mInjector = injector;
mContext = context;
mClock = injector.getClock();
@@ -130,7 +137,6 @@
mBrightnessMin = brightnessMin;
mBrightnessMax = brightnessMax;
mHbmChangeCallback = hbmChangeCallback;
- mHighBrightnessModeMetadata = hbmMetadata;
mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
mRecalcRunnable = this::recalculateTimeAllowance;
@@ -216,22 +222,19 @@
// If we are starting or ending a high brightness mode session, store the current
// session in mRunningStartTimeMillis, or the old one in mEvents.
- final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
- final boolean wasHbmDrainingAvailableTime = runningStartTime != -1;
+ final boolean wasHbmDrainingAvailableTime = mRunningStartTimeMillis != -1;
final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint
&& !mIsHdrLayerPresent;
if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
final long currentTime = mClock.uptimeMillis();
if (shouldHbmDrainAvailableTime) {
- mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
+ mRunningStartTimeMillis = currentTime;
} else {
- final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime);
- mHighBrightnessModeMetadata.addHbmEvent(hbmEvent);
- mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1);
+ mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime));
+ mRunningStartTimeMillis = -1;
if (DEBUG) {
- Slog.d(TAG, "New HBM event: "
- + mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst());
+ Slog.d(TAG, "New HBM event: " + mEvents.peekFirst());
}
}
}
@@ -257,10 +260,6 @@
mSettingsObserver.stopObserving();
}
- void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) {
- mHighBrightnessModeMetadata = hbmInfo;
- }
-
void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) {
mWidth = width;
@@ -317,22 +316,20 @@
pw.println(" mBrightnessMax=" + mBrightnessMax);
pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
pw.println(" mIsTimeAvailable= " + mIsTimeAvailable);
- pw.println(" mRunningStartTimeMillis="
- + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
+ pw.println(" mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis));
pw.println(" mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
pw.println(" width*height=" + mWidth + "*" + mHeight);
pw.println(" mEvents=");
final long currentTime = mClock.uptimeMillis();
long lastStartTime = currentTime;
- long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
- if (runningStartTimeMillis != -1) {
- lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime));
+ if (mRunningStartTimeMillis != -1) {
+ lastStartTime = dumpHbmEvent(pw, new HbmEvent(mRunningStartTimeMillis, currentTime));
}
- for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) {
- if (lastStartTime > event.getEndTimeMillis()) {
+ for (HbmEvent event : mEvents) {
+ if (lastStartTime > event.endTimeMillis) {
pw.println(" event: [normal brightness]: "
- + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis()));
+ + TimeUtils.formatDuration(lastStartTime - event.endTimeMillis));
}
lastStartTime = dumpHbmEvent(pw, event);
}
@@ -341,12 +338,12 @@
}
private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
- final long duration = event.getEndTimeMillis() - event.getStartTimeMillis();
+ final long duration = event.endTimeMillis - event.startTimeMillis;
pw.println(" event: ["
- + TimeUtils.formatUptime(event.getStartTimeMillis()) + ", "
- + TimeUtils.formatUptime(event.getEndTimeMillis()) + "] ("
+ + TimeUtils.formatUptime(event.startTimeMillis) + ", "
+ + TimeUtils.formatUptime(event.endTimeMillis) + "] ("
+ TimeUtils.formatDuration(duration) + ")");
- return event.getStartTimeMillis();
+ return event.startTimeMillis;
}
private boolean isCurrentlyAllowed() {
@@ -375,15 +372,13 @@
// First, lets see how much time we've taken for any currently running
// session of HBM.
- long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
- if (runningStartTimeMillis > 0) {
- if (runningStartTimeMillis > currentTime) {
+ if (mRunningStartTimeMillis > 0) {
+ if (mRunningStartTimeMillis > currentTime) {
Slog.e(TAG, "Start time set to the future. curr: " + currentTime
- + ", start: " + runningStartTimeMillis);
- mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
- runningStartTimeMillis = currentTime;
+ + ", start: " + mRunningStartTimeMillis);
+ mRunningStartTimeMillis = currentTime;
}
- timeAlreadyUsed = currentTime - runningStartTimeMillis;
+ timeAlreadyUsed = currentTime - mRunningStartTimeMillis;
}
if (DEBUG) {
@@ -392,19 +387,18 @@
// Next, lets iterate through the history of previous sessions and add those times.
final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
- Iterator<HbmEvent> it = mHighBrightnessModeMetadata.getHbmEventQueue().iterator();
+ Iterator<HbmEvent> it = mEvents.iterator();
while (it.hasNext()) {
final HbmEvent event = it.next();
// If this event ended before the current Timing window, discard forever and ever.
- if (event.getEndTimeMillis() < windowstartTimeMillis) {
+ if (event.endTimeMillis < windowstartTimeMillis) {
it.remove();
continue;
}
- final long startTimeMillis = Math.max(event.getStartTimeMillis(),
- windowstartTimeMillis);
- timeAlreadyUsed += event.getEndTimeMillis() - startTimeMillis;
+ final long startTimeMillis = Math.max(event.startTimeMillis, windowstartTimeMillis);
+ timeAlreadyUsed += event.endTimeMillis - startTimeMillis;
}
if (DEBUG) {
@@ -431,18 +425,17 @@
// Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or
// brightness change doesn't happen before then.
long nextTimeout = -1;
- final ArrayDeque<HbmEvent> hbmEvents = mHighBrightnessModeMetadata.getHbmEventQueue();
if (mBrightness > mHbmData.transitionPoint) {
// if we're in high-lux now, timeout when we run out of allowed time.
nextTimeout = currentTime + remainingTime;
- } else if (!mIsTimeAvailable && hbmEvents.size() > 0) {
+ } else if (!mIsTimeAvailable && mEvents.size() > 0) {
// If we are not allowed...timeout when the oldest event moved outside of the timing
// window by at least minTime. Basically, we're calculating the soonest time we can
// get {@code timeMinMillis} back to us.
final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
- final HbmEvent lastEvent = hbmEvents.peekLast();
+ final HbmEvent lastEvent = mEvents.peekLast();
final long startTimePlusMinMillis =
- Math.max(windowstartTimeMillis, lastEvent.getStartTimeMillis())
+ Math.max(windowstartTimeMillis, lastEvent.startTimeMillis)
+ mHbmData.timeMinMillis;
final long timeWhenMinIsGainedBack =
currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime;
@@ -466,10 +459,9 @@
+ ", mUnthrottledBrightness: " + mUnthrottledBrightness
+ ", mThrottlingReason: "
+ BrightnessInfo.briMaxReasonToString(mThrottlingReason)
- + ", RunningStartTimeMillis: "
- + mHighBrightnessModeMetadata.getRunningStartTimeMillis()
+ + ", RunningStartTimeMillis: " + mRunningStartTimeMillis
+ ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
- + ", events: " + hbmEvents);
+ + ", events: " + mEvents);
}
if (nextTimeout != -1) {
@@ -596,6 +588,25 @@
}
}
+ /**
+ * Represents an event in which High Brightness Mode was enabled.
+ */
+ private static class HbmEvent {
+ public long startTimeMillis;
+ public long endTimeMillis;
+
+ HbmEvent(long startTimeMillis, long endTimeMillis) {
+ this.startTimeMillis = startTimeMillis;
+ this.endTimeMillis = endTimeMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "[Event: {" + startTimeMillis + ", " + endTimeMillis + "}, total: "
+ + ((endTimeMillis - startTimeMillis) / 1000) + "]";
+ }
+ }
+
@VisibleForTesting
class HdrListener extends SurfaceControlHdrLayerInfoListener {
@Override
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java
deleted file mode 100644
index 37234ff..0000000
--- a/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java
+++ /dev/null
@@ -1,58 +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.server.display;
-
-import java.util.ArrayDeque;
-
-
-/**
- * Represents High Brightness Mode metadata associated
- * with a specific internal physical display.
- * Required for separately storing data like time information,
- * and related events when display was in HBM mode per
- * physical internal display.
- */
-class HighBrightnessModeMetadata {
- /**
- * Queue of previous HBM-events ordered from most recent to least recent.
- * Meant to store only the events that fall into the most recent
- * {@link HighBrightnessModeData#timeWindowMillis mHbmData.timeWindowMillis}.
- */
- private final ArrayDeque<HbmEvent> mEvents = new ArrayDeque<>();
-
- /**
- * If HBM is currently running, this is the start time for the current HBM session.
- */
- private long mRunningStartTimeMillis = -1;
-
- public long getRunningStartTimeMillis() {
- return mRunningStartTimeMillis;
- }
-
- public void setRunningStartTimeMillis(long setTime) {
- mRunningStartTimeMillis = setTime;
- }
-
- public ArrayDeque<HbmEvent> getHbmEventQueue() {
- return mEvents;
- }
-
- public void addHbmEvent(HbmEvent hbmEvent) {
- mEvents.addFirst(hbmEvent);
- }
-}
-
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index bb342ff..5087caa 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -177,6 +177,7 @@
private Layout mCurrentLayout = null;
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+ private int mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
private boolean mBootCompleted = false;
private boolean mInteractive;
@@ -373,6 +374,12 @@
ipw.println("mDeviceStatesOnWhichToWakeUp=" + mDeviceStatesOnWhichToWakeUp);
ipw.println("mDeviceStatesOnWhichToSleep=" + mDeviceStatesOnWhichToSleep);
ipw.println("mInteractive=" + mInteractive);
+ ipw.println("mBootCompleted=" + mBootCompleted);
+
+ ipw.println();
+ ipw.println("mDeviceState=" + mDeviceState);
+ ipw.println("mPendingDeviceState=" + mPendingDeviceState);
+ ipw.println("mDeviceStateToBeAppliedAfterBoot=" + mDeviceStateToBeAppliedAfterBoot);
final int logicalDisplayCount = mLogicalDisplays.size();
ipw.println();
@@ -403,10 +410,6 @@
}
void setDeviceStateLocked(int state, boolean isOverrideActive) {
- Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
- + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
- mPendingDeviceState = state;
-
if (!mBootCompleted) {
// The boot animation might still be in progress, we do not want to switch states now
// as the boot animation would end up with an incorrect size.
@@ -414,14 +417,19 @@
Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState
+ " until boot is completed");
}
+ mDeviceStateToBeAppliedAfterBoot = state;
return;
}
+ Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
+ + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
// As part of a state transition, we may need to turn off some displays temporarily so that
// the transition is smooth. Plus, on some devices, only one internal displays can be
// on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
// temporarily turned off.
resetLayoutLocked(mDeviceState, state, /* transitionValue= */ true);
+ mPendingDeviceState = state;
+ mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
@@ -468,8 +476,9 @@
void onBootCompleted() {
synchronized (mSyncRoot) {
mBootCompleted = true;
- if (mPendingDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
- setDeviceStateLocked(mPendingDeviceState, /* isOverrideActive= */ false);
+ if (mDeviceStateToBeAppliedAfterBoot != DeviceStateManager.INVALID_DEVICE_STATE) {
+ setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot,
+ /* isOverrideActive= */ false);
}
}
}
@@ -524,7 +533,8 @@
@VisibleForTesting
boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive,
boolean isInteractive, boolean isBootCompleted) {
- return mDeviceStatesOnWhichToSleep.get(pendingState)
+ return currentState != DeviceStateManager.INVALID_DEVICE_STATE
+ && mDeviceStatesOnWhichToSleep.get(pendingState)
&& !mDeviceStatesOnWhichToSleep.get(currentState)
&& !isOverrideActive
&& isInteractive && isBootCompleted;
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
index fd4e296..d5aeba1 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
@@ -17,6 +17,7 @@
package com.android.server.display.brightness;
import android.os.PowerManager;
+import android.util.MathUtils;
import com.android.server.display.DisplayBrightnessState;
@@ -33,6 +34,14 @@
}
/**
+ * Clamps the brightness value in the maximum and the minimum brightness range
+ */
+ public static float clampAbsoluteBrightness(float value) {
+ return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
+ PowerManager.BRIGHTNESS_MAX);
+ }
+
+ /**
* A utility to construct the DisplayBrightnessState
*/
public static DisplayBrightnessState constructDisplayBrightnessState(
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index bdc8d9d..e003ecb 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -18,9 +18,12 @@
import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
import android.util.IndentingPrintWriter;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.BrightnessSetting;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
@@ -31,19 +34,67 @@
* display. Applies the chosen brightness.
*/
public final class DisplayBrightnessController {
+ // The ID of the display tied to this DisplayBrightnessController
private final int mDisplayId;
+
+ // The lock which is to be used to synchronize the resources being used in this class
+ private final Object mLock = new Object();
+
+ // The default screen brightness to be used when no value is available in BrightnessSetting.
+ private final float mScreenBrightnessDefault;
+
+ // This is used to persist the changes happening to the brightness.
+ private final BrightnessSetting mBrightnessSetting;
+
+ // A runnable to update the clients registered via DisplayManagerGlobal
+ // .EVENT_DISPLAY_BRIGHTNESS_CHANGED about the brightness change. Called when
+ // mCurrentScreenBrightness is updated.
+ private Runnable mOnBrightnessChangeRunnable;
+
+ // The screen brightness that has changed but not taken effect yet. If this is different
+ // from the current screen brightness then this is coming from something other than us
+ // and should be considered a user interaction.
+ @GuardedBy("mLock")
+ private float mPendingScreenBrightness;
+
+ // The last observed screen brightness, either set by us or by the settings app on
+ // behalf of the user.
+ @GuardedBy("mLock")
+ private float mCurrentScreenBrightness;
+
+ // The last brightness that was set by the user and not temporary. Set to
+ // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
+ @GuardedBy("mLock")
+ private float mLastUserSetScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+
+ // The listener which is to be notified everytime there is a change in the brightness in the
+ // BrightnessSetting.
+ private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
+
// Selects an appropriate strategy based on the request provided by the clients.
+ @GuardedBy("mLock")
private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+
+ // Currently selected DisplayBrightnessStrategy.
+ @GuardedBy("mLock")
private DisplayBrightnessStrategy mDisplayBrightnessStrategy;
/**
* The constructor of DisplayBrightnessController.
*/
- public DisplayBrightnessController(Context context, Injector injector, int displayId) {
+ public DisplayBrightnessController(Context context, Injector injector, int displayId,
+ float defaultScreenBrightness, BrightnessSetting brightnessSetting,
+ Runnable onBrightnessChangeRunnable) {
if (injector == null) {
injector = new Injector();
}
mDisplayId = displayId;
+ // TODO: b/186428377 update brightness setting when display changes
+ mBrightnessSetting = brightnessSetting;
+ mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mCurrentScreenBrightness = getScreenBrightnessSetting();
+ mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
+ mScreenBrightnessDefault = BrightnessUtils.clampAbsoluteBrightness(defaultScreenBrightness);
mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context,
displayId);
}
@@ -61,25 +112,20 @@
public DisplayBrightnessState updateBrightness(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState) {
- mDisplayBrightnessStrategy =
- mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
- targetDisplayState);
- return mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+ synchronized (mLock) {
+ mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
+ displayPowerRequest, targetDisplayState);
+ return mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+ }
}
/**
* Sets the temporary brightness
*/
public void setTemporaryBrightness(Float temporaryBrightness) {
- mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
- .setTemporaryScreenBrightness(temporaryBrightness);
- }
-
- /**
- * Returns the current selected DisplayBrightnessStrategy
- */
- public DisplayBrightnessStrategy getCurrentDisplayBrightnessStrategy() {
- return mDisplayBrightnessStrategy;
+ synchronized (mLock) {
+ setTemporaryBrightnessLocked(temporaryBrightness);
+ }
}
/**
@@ -87,7 +133,140 @@
* brightness when dozing
*/
public boolean isAllowAutoBrightnessWhileDozingConfig() {
- return mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozingConfig();
+ synchronized (mLock) {
+ return mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozingConfig();
+ }
+ }
+
+ /**
+ * Sets the current screen brightness to the supplied value, and notifies all the listeners
+ * requesting for change events on brightness change.
+ */
+ public void setCurrentScreenBrightness(float brightnessValue) {
+ synchronized (mLock) {
+ if (brightnessValue != mCurrentScreenBrightness) {
+ mCurrentScreenBrightness = brightnessValue;
+ mOnBrightnessChangeRunnable.run();
+ }
+ }
+ }
+
+ /**
+ * Returns the last observed screen brightness.
+ */
+ public float getCurrentBrightness() {
+ synchronized (mLock) {
+ return mCurrentScreenBrightness;
+ }
+ }
+
+ /**
+ * Returns the screen brightness which has changed but has not taken any effect so far.
+ */
+ public float getPendingScreenBrightness() {
+ synchronized (mLock) {
+ return mPendingScreenBrightness;
+ }
+ }
+
+ /**
+ * Sets the pending screen brightness setting, representing a value which is requested, but not
+ * yet processed.
+ * @param brightnessValue The value to which the pending screen brightness is to be set.
+ */
+ public void setPendingScreenBrightness(float brightnessValue) {
+ synchronized (mLock) {
+ mPendingScreenBrightness = brightnessValue;
+ }
+ }
+
+ /**
+ * We want to return true if the user has set the screen brightness.
+ * RBC on, off, and intensity changes will return false.
+ * Slider interactions whilst in RBC will return true, just as when in non-rbc.
+ */
+ public boolean updateUserSetScreenBrightness() {
+ synchronized (mLock) {
+ if (!BrightnessUtils.isValidBrightnessValue(mPendingScreenBrightness)) {
+ return false;
+ }
+ if (mCurrentScreenBrightness == mPendingScreenBrightness) {
+ mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ return false;
+ }
+ setCurrentScreenBrightness(mPendingScreenBrightness);
+ mLastUserSetScreenBrightness = mPendingScreenBrightness;
+ mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ setTemporaryBrightnessLocked(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ return true;
+ }
+ }
+
+ /**
+ * Registers the BrightnessSettingListener with the BrightnessSetting, which will be notified
+ * everytime there is a change in the brightness.
+ */
+ public void registerBrightnessSettingChangeListener(
+ BrightnessSetting.BrightnessSettingListener brightnessSettingListener) {
+ mBrightnessSettingListener = brightnessSettingListener;
+ mBrightnessSetting.registerListener(mBrightnessSettingListener);
+ }
+
+ /**
+ * Returns the last user set brightness which is not temporary.
+ */
+ public float getLastUserSetScreenBrightness() {
+ synchronized (mLock) {
+ return mLastUserSetScreenBrightness;
+ }
+ }
+
+ /**
+ * Returns the current screen brightnessSetting which is responsible for saving the brightness
+ * in the persistent store
+ */
+ public float getScreenBrightnessSetting() {
+ float brightness = mBrightnessSetting.getBrightness();
+ synchronized (mLock) {
+ if (Float.isNaN(brightness)) {
+ brightness = mScreenBrightnessDefault;
+ }
+ return BrightnessUtils.clampAbsoluteBrightness(brightness);
+ }
+ }
+
+ /**
+ * Notifies the brightnessSetting to persist the supplied brightness value.
+ */
+ public void setBrightness(float brightnessValue) {
+ // Update the setting, which will eventually call back into DPC to have us actually
+ // update the display with the new value.
+ mBrightnessSetting.setBrightness(brightnessValue);
+ }
+
+ /**
+ * Sets the current screen brightness, and notifies the BrightnessSetting about the change.
+ */
+ public void updateScreenBrightnessSetting(float brightnessValue) {
+ synchronized (mLock) {
+ if (!BrightnessUtils.isValidBrightnessValue(brightnessValue)
+ || brightnessValue == mCurrentScreenBrightness) {
+ return;
+ }
+ setCurrentScreenBrightness(brightnessValue);
+ setBrightness(brightnessValue);
+ }
+ }
+
+ /**
+ * Stops the associated listeners when the display is stopped. Invoked when the {@link
+ * #mDisplayId} is being removed.
+ */
+ public void stop() {
+ if (mBrightnessSetting != null) {
+ mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
+ }
}
/**
@@ -99,12 +278,19 @@
writer.println();
writer.println("DisplayBrightnessController:");
writer.println(" mDisplayId=: " + mDisplayId);
- if (mDisplayBrightnessStrategy != null) {
- writer.println(" Last selected DisplayBrightnessStrategy= "
- + mDisplayBrightnessStrategy.getName());
+ writer.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault);
+ synchronized (mLock) {
+ writer.println(" mPendingScreenBrightness=" + mPendingScreenBrightness);
+ writer.println(" mCurrentScreenBrightness=" + mCurrentScreenBrightness);
+ writer.println(" mLastUserSetScreenBrightness="
+ + mLastUserSetScreenBrightness);
+ if (mDisplayBrightnessStrategy != null) {
+ writer.println(" Last selected DisplayBrightnessStrategy= "
+ + mDisplayBrightnessStrategy.getName());
+ }
+ IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+ mDisplayBrightnessStrategySelector.dump(ipw);
}
- IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
- mDisplayBrightnessStrategySelector.dump(ipw);
}
@VisibleForTesting
@@ -114,4 +300,26 @@
return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId);
}
}
+
+ @VisibleForTesting
+ BrightnessSetting.BrightnessSettingListener getBrightnessSettingListenerLocked() {
+ return mBrightnessSettingListener;
+ }
+
+ /**
+ * Returns the current selected DisplayBrightnessStrategy
+ */
+ @VisibleForTesting
+ DisplayBrightnessStrategy getCurrentDisplayBrightnessStrategyLocked() {
+ synchronized (mLock) {
+ return mDisplayBrightnessStrategy;
+ }
+ }
+
+ private void setTemporaryBrightnessLocked(float temporaryBrightness) {
+ synchronized (mLock) {
+ mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
+ .setTemporaryScreenBrightness(temporaryBrightness);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index c47d749..a4d2d03 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -462,7 +462,7 @@
}
protected void requestStopDreamFromShell() {
- stopDreamInternal(true, "stopping dream from shell");
+ stopDreamInternal(false, "stopping dream from shell");
}
private void stopDreamInternal(boolean immediate, String reason) {
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 993b4fd..ff9ce6f 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -234,7 +234,8 @@
}
private boolean isUsiDevice(int deviceId) {
- return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::supportsUsi);
+ return processInputDevice(deviceId, false /*defaultValue*/,
+ (device) -> device.getHostUsiVersion() != null);
}
@Nullable
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index 60167b4..da65f27 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -59,13 +59,16 @@
private static final long TIMEOUT_MS = 10_000;
/** Handler for registering timeouts for live entries. */
- private final Handler mHandler =
- new Handler(Looper.myLooper(), null /* callback */, true /* async */);
+ private final Handler mHandler;
/** Singleton instance of the History. */
@GuardedBy("ImeTrackerService.this")
private final History mHistory = new History();
+ ImeTrackerService(@NonNull Looper looper) {
+ mHandler = new Handler(looper, null /* callback */, true /* async */);
+ }
+
@NonNull
@Override
public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index a63b95c..2ced988 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -89,6 +89,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.Process;
@@ -1105,7 +1106,7 @@
new SoftInputShowHideHistory();
@NonNull
- private final ImeTrackerService mImeTrackerService = new ImeTrackerService();
+ private final ImeTrackerService mImeTrackerService;
class SettingsObserver extends ContentObserver {
int mUserId;
@@ -1662,6 +1663,8 @@
true /* allowIo */);
thread.start();
mHandler = Handler.createAsync(thread.getLooper(), this);
+ mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
+ ? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
// Note: SettingsObserver doesn't register observers in its constructor.
mSettingsObserver = new SettingsObserver(mHandler);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
@@ -4613,7 +4616,9 @@
info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
info.imeSurfaceParentName));
- mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
+ if (statsToken != null) {
+ mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
+ }
}
@BinderThread
@@ -4655,13 +4660,10 @@
}
@VisibleForTesting
- ImeVisibilityStateComputer getVisibilityStateComputer() {
- return mVisibilityStateComputer;
- }
-
- @VisibleForTesting
ImeVisibilityApplier getVisibilityApplier() {
- return mVisibilityApplier;
+ synchronized (ImfLock.class) {
+ return mVisibilityApplier;
+ }
}
@GuardedBy("ImfLock.class")
diff --git a/services/core/java/com/android/server/inputmethod/TEST_MAPPING b/services/core/java/com/android/server/inputmethod/TEST_MAPPING
index 0ccd75d..bd95c5b 100644
--- a/services/core/java/com/android/server/inputmethod/TEST_MAPPING
+++ b/services/core/java/com/android/server/inputmethod/TEST_MAPPING
@@ -2,6 +2,9 @@
"imports": [
{
"path": "frameworks/base/core/java/android/view/inputmethod"
+ },
+ {
+ "path": "frameworks/base/services/tests/InputMethodSystemServerTests"
}
]
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 7f6c2d6..8f65775 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -1155,6 +1155,34 @@
return nanoappIds;
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ /**
+ * Puts the context hub in and out of test mode. Test mode is a clean state
+ * where tests can be executed in the same environment. If enable is true,
+ * this will enable test mode by unloading all nanoapps. If enable is false,
+ * this will disable test mode and reverse the actions of enabling test mode
+ * by loading all preloaded nanoapps. This puts CHRE in a normal state.
+ *
+ * This should only be used for a test environment, either through a
+ * @TestApi or development tools. This should not be used in a production
+ * environment.
+ *
+ * @param enable If true, put the context hub in test mode. If false, disable
+ * test mode.
+ * @return If true, the operation was successful; false otherwise.
+ */
+ @Override
+ public boolean setTestMode(boolean enable) {
+ super.setTestMode_enforcePermission();
+ boolean status = mContextHubWrapper.setTestMode(enable);
+
+ // Query nanoapps to update service state after test mode state change.
+ for (int contextHubId: mDefaultClientMap.keySet()) {
+ queryNanoAppsInternal(contextHubId);
+ }
+ return status;
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 3581b02..ca184ee 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -382,6 +382,23 @@
*/
public abstract void registerExistingCallback(int contextHubId) throws RemoteException;
+ /**
+ * Puts the context hub in and out of test mode. Test mode is a clean state
+ * where tests can be executed in the same environment. If enable is true,
+ * this will enable test mode by unloading all nanoapps. If enable is false,
+ * this will disable test mode and reverse the actions of enabling test mode
+ * by loading all preloaded nanoapps. This puts CHRE in a normal state.
+ *
+ * This should only be used for a test environment, either through a
+ * @TestApi or development tools. This should not be used in a production
+ * environment.
+ *
+ * @param enable If true, put the context hub in test mode. If false, disable
+ * test mode.
+ * @return If true, the operation was successful; false otherwise.
+ */
+ public abstract boolean setTestMode(boolean enable);
+
private static class ContextHubWrapperAidl extends IContextHubWrapper
implements IBinder.DeathRecipient {
private android.hardware.contexthub.IContextHub mHub;
@@ -741,6 +758,22 @@
registerExistingCallback(contextHubId);
}
+ public boolean setTestMode(boolean enable) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return false;
+ }
+
+ try {
+ hub.setTestMode(enable);
+ return true;
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Exception while setting test mode (enable: "
+ + (enable ? "true" : "false") + "): " + e.getMessage());
+ return false;
+ }
+ }
+
private void onSettingChanged(byte setting, boolean enabled) {
android.hardware.contexthub.IContextHub hub = getHub();
if (hub == null) {
@@ -911,6 +944,10 @@
mHub.registerCallback(contextHubId, callback);
}
+ public boolean setTestMode(boolean enable) {
+ return false;
+ }
+
public boolean supportsBtSettingNotifications() {
return false;
}
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 56390a9..15cfca5 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -274,20 +274,16 @@
}
}
- final long endMillis = SystemClock.elapsedRealtime() + SERVICE_CONNECT_TIMEOUT_MS;
- while (SystemClock.elapsedRealtime() <= endMillis) {
+ final long endMillis = SystemClock.uptimeMillis() + SERVICE_CONNECT_TIMEOUT_MS;
+ do {
final IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
if (binder != null) {
binder.linkToDeath(
() -> Slog.w(TAG, String.format("service '%s' died", IDMAP_SERVICE)), 0);
return binder;
}
-
- try {
- Thread.sleep(SERVICE_CONNECT_INTERVAL_SLEEP_MS);
- } catch (InterruptedException ignored) {
- }
- }
+ SystemClock.sleep(SERVICE_CONNECT_INTERVAL_SLEEP_MS);
+ } while (SystemClock.uptimeMillis() <= endMillis);
throw new TimeoutException(
String.format("Failed to connect to '%s' in %d milliseconds", IDMAP_SERVICE,
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 5c4c7c9..faa06f7 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -331,6 +331,7 @@
/** Adds listener for package update */
public void addPackagesUpdatedListener(PackagesUpdatedListener listener)
throws LegacyDexoptDisabledException {
+ // TODO(b/251903639): Evaluate whether this needs to support ART Service or not.
Installer.checkLegacyDexoptDisabled();
synchronized (mLock) {
mPackagesUpdatedListeners.add(listener);
@@ -629,6 +630,8 @@
/** Gets the size of a package. */
private long getPackageSize(@NonNull Computer snapshot, String pkg) {
+ // TODO(b/251903639): Make this in line with the calculation in
+ // `DexOptHelper.DexoptDoneHandler`.
PackageInfo info = snapshot.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
long size = 0;
if (info != null && info.applicationInfo != null) {
@@ -723,6 +726,8 @@
return optimizePackages(pkgs, lowStorageThreshold, updatedPackages, isPostBootUpdate);
} finally {
// Always let the pinner service know about changes.
+ // TODO(b/251903639): ART Service does this for all dexopts, while the code below only
+ // runs for background jobs. We should try to make them behave the same.
notifyPinService(updatedPackages);
// Only notify IORap the primary dex opt, because we don't want to
// invalidate traces unnecessary due to b/161633001 and that it's
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index c2f0f52..094b182 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2998,35 +2998,40 @@
}
ipw.println("Dexopt state:");
ipw.increaseIndent();
- Collection<? extends PackageStateInternal> pkgSettings;
- if (setting != null) {
- pkgSettings = Collections.singletonList(setting);
+ if (DexOptHelper.useArtService()) {
+ DexOptHelper.dumpDexoptState(ipw, packageName);
} else {
- pkgSettings = mSettings.getPackages().values();
- }
-
- for (PackageStateInternal pkgSetting : pkgSettings) {
- final AndroidPackage pkg = pkgSetting.getPkg();
- if (pkg == null || pkg.isApex()) {
- // Skip APEX which is not dex-optimized
- continue;
+ Collection<? extends PackageStateInternal> pkgSettings;
+ if (setting != null) {
+ pkgSettings = Collections.singletonList(setting);
+ } else {
+ pkgSettings = mSettings.getPackages().values();
}
- final String pkgName = pkg.getPackageName();
- ipw.println("[" + pkgName + "]");
+
+ for (PackageStateInternal pkgSetting : pkgSettings) {
+ final AndroidPackage pkg = pkgSetting.getPkg();
+ if (pkg == null || pkg.isApex()) {
+ // Skip APEX which is not dex-optimized
+ continue;
+ }
+ final String pkgName = pkg.getPackageName();
+ ipw.println("[" + pkgName + "]");
+ ipw.increaseIndent();
+
+ // TODO(b/251903639): Call into ART Service.
+ try {
+ mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
+ mDexManager.getPackageUseInfoOrDefault(pkgName));
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
+ ipw.decreaseIndent();
+ }
+ ipw.println("BgDexopt state:");
ipw.increaseIndent();
-
- // TODO(b/251903639): Call into ART Service.
- try {
- mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
- mDexManager.getPackageUseInfoOrDefault(pkgName));
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
+ mBackgroundDexOptService.dump(ipw);
ipw.decreaseIndent();
}
- ipw.println("BgDexopt state:");
- ipw.increaseIndent();
- mBackgroundDexOptService.dump(ipw);
ipw.decreaseIndent();
break;
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 5dd6fe7..de37080 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -57,9 +57,14 @@
import android.util.Slog;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalManagerRegistry;
+import com.android.server.LocalServices;
+import com.android.server.PinnerService;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.DexUseManagerLocal;
+import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
@@ -81,7 +86,6 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
-import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@@ -94,6 +98,10 @@
private final PackageManagerService mPm;
+ // Start time for the boot dexopt in performPackageDexOptUpgradeIfNeeded when ART Service is
+ // used, to make it available to the onDexoptDone callback.
+ private volatile long mBootDexoptStartTime;
+
DexOptHelper(PackageManagerService pm) {
mPm = pm;
}
@@ -210,6 +218,7 @@
pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
}
+ // TODO(b/251903639): Do this when ART Service is used, or remove it from here.
if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) {
mPm.mArtManagerService.compileLayouts(packageState, pkg);
}
@@ -317,7 +326,7 @@
* Called during startup to do any boot time dexopting. This can occasionally be time consuming
* (30+ seconds) and the function will block until it is complete.
*/
- public void performPackageDexOptUpgradeIfNeeded() throws LegacyDexoptDisabledException {
+ public void performPackageDexOptUpgradeIfNeeded() {
PackageManagerServiceUtils.enforceSystemOrRoot(
"Only the system can request package update");
@@ -332,29 +341,49 @@
return;
}
- // System UI is important to user experience, so we check it after a mainline update
- // or an OTA. It may need to be re-compiled in these cases.
- checkAndDexOptSystemUi(reason);
-
- if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
- return;
- }
-
- final Computer snapshot = mPm.snapshotComputer();
- List<PackageStateInternal> pkgSettings =
- getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
-
final long startTime = System.nanoTime();
- final int[] stats = performDexOptUpgrade(pkgSettings, reason, false /* bootComplete */);
+ if (useArtService()) {
+ mBootDexoptStartTime = startTime;
+ getArtManagerLocal().onBoot(DexoptOptions.convertToArtServiceDexoptReason(reason),
+ null /* progressCallbackExecutor */, null /* progressCallback */);
+ } else {
+ try {
+ // System UI is important to user experience, so we check it after a mainline update
+ // or an OTA. It may need to be re-compiled in these cases.
+ checkAndDexOptSystemUi(reason);
+
+ if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
+ return;
+ }
+
+ final Computer snapshot = mPm.snapshotComputer();
+
+ // TODO(b/251903639): Align this with how ART Service selects packages for boot
+ // compilation.
+ List<PackageStateInternal> pkgSettings =
+ getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
+
+ final int[] stats =
+ performDexOptUpgrade(pkgSettings, reason, false /* bootComplete */);
+ reportBootDexopt(startTime, stats[0], stats[1], stats[2]);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private void reportBootDexopt(long startTime, int numDexopted, int numSkipped, int numFailed) {
final int elapsedTimeSeconds =
(int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
final Computer newSnapshot = mPm.snapshotComputer();
- MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", stats[0]);
- MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", stats[1]);
- MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", stats[2]);
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", numDexopted);
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", numSkipped);
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", numFailed);
+ // TODO(b/251903639): getOptimizablePackages calls PackageDexOptimizer.canOptimizePackage
+ // which duplicates logic in ART Service (com.android.server.art.Utils.canDexoptPackage).
MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_total",
getOptimizablePackages(newSnapshot).size());
MetricsLogger.histogram(mPm.mContext, "opt_dialog_time_s", elapsedTimeSeconds);
@@ -386,9 +415,8 @@
@DexOptResult int dexoptStatus;
if (options.isDexoptOnlySecondaryDex()) {
- Optional<Integer> artSrvRes = performDexOptWithArtService(options, 0 /* extraFlags */);
- if (artSrvRes.isPresent()) {
- dexoptStatus = artSrvRes.get();
+ if (useArtService()) {
+ dexoptStatus = performDexOptWithArtService(options, 0 /* extraFlags */);
} else {
try {
return mPm.getDexManager().dexoptSecondaryDex(options);
@@ -428,10 +456,8 @@
// if the package can now be considered up to date for the given filter.
@DexOptResult
private int performDexOptInternal(DexoptOptions options) {
- Optional<Integer> artSrvRes =
- performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
- if (artSrvRes.isPresent()) {
- return artSrvRes.get();
+ if (useArtService()) {
+ return performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
}
AndroidPackage p;
@@ -461,46 +487,26 @@
}
/**
- * Performs dexopt on the given package using ART Service.
- *
- * @return a {@link DexOptResult}, or empty if the request isn't supported so that it is
- * necessary to fall back to the legacy code paths.
+ * Performs dexopt on the given package using ART Service. May only be called when ART Service
+ * is enabled, i.e. when {@link useArtService} returns true.
*/
- private Optional<Integer> performDexOptWithArtService(DexoptOptions options,
+ @DexOptResult
+ private int performDexOptWithArtService(DexoptOptions options,
/*@DexoptFlags*/ int extraFlags) {
- ArtManagerLocal artManager = getArtManagerLocal();
- if (artManager == null) {
- return Optional.empty();
- }
-
try (PackageManagerLocal.FilteredSnapshot snapshot =
getPackageManagerLocal().withFilteredSnapshot()) {
PackageState ops = snapshot.getPackageState(options.getPackageName());
if (ops == null) {
- return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+ return PackageDexOptimizer.DEX_OPT_FAILED;
}
AndroidPackage oap = ops.getAndroidPackage();
if (oap == null) {
- return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+ return PackageDexOptimizer.DEX_OPT_FAILED;
}
- if (oap.isApex()) {
- return Optional.of(PackageDexOptimizer.DEX_OPT_SKIPPED);
- }
-
DexoptParams params = options.convertToDexoptParams(extraFlags);
- if (params == null) {
- return Optional.empty();
- }
-
- DexoptResult result;
- try {
- result = artManager.dexoptPackage(snapshot, options.getPackageName(), params);
- } catch (UnsupportedOperationException e) {
- reportArtManagerFallback(options.getPackageName(), e.toString());
- return Optional.empty();
- }
-
- return Optional.of(convertToDexOptResult(result));
+ DexoptResult result =
+ getArtManagerLocal().dexoptPackage(snapshot, options.getPackageName(), params);
+ return convertToDexOptResult(result);
}
}
@@ -578,14 +584,12 @@
getDefaultCompilerFilter(), null /* splitName */,
DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
- // performDexOptWithArtService ignores the snapshot and takes its own, so it can race with
- // the package checks above, but at worst the effect is only a bit less friendly error
- // below.
- Optional<Integer> artSrvRes =
- performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
- int res;
- if (artSrvRes.isPresent()) {
- res = artSrvRes.get();
+ @DexOptResult int res;
+ if (useArtService()) {
+ // performDexOptWithArtService ignores the snapshot and takes its own, so it can race
+ // with the package checks above, but at worst the effect is only a bit less friendly
+ // error below.
+ res = performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
} else {
try {
res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
@@ -844,6 +848,26 @@
}
/**
+ * Dumps the dexopt state for the given package, or all packages if it is null.
+ */
+ public static void dumpDexoptState(
+ @NonNull IndentingPrintWriter ipw, @Nullable String packageName) {
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ getPackageManagerLocal().withFilteredSnapshot()) {
+ if (packageName != null) {
+ try {
+ DexOptHelper.getArtManagerLocal().dumpPackage(ipw, snapshot, packageName);
+ } catch (IllegalArgumentException e) {
+ // Package isn't found, but that should only happen due to race.
+ ipw.println(e);
+ }
+ } else {
+ DexOptHelper.getArtManagerLocal().dump(ipw, snapshot);
+ }
+ }
+ }
+
+ /**
* Returns the module names of the APEXes that contribute to bootclasspath.
*/
private static List<String> getBcpApexes() {
@@ -882,15 +906,6 @@
}
/**
- * Called whenever we need to fall back from ART Service to the legacy dexopt code.
- */
- public static void reportArtManagerFallback(String packageName, String reason) {
- // STOPSHIP(b/251903639): Minimize these calls to avoid platform getting shipped with code
- // paths that will always bypass ART Service.
- Slog.i(TAG, "Falling back to old PackageManager dexopt for " + packageName + ": " + reason);
- }
-
- /**
* Returns true if ART Service should be used for package optimization.
*/
public static boolean useArtService() {
@@ -918,6 +933,32 @@
*/
@Override
public void onDexoptDone(@NonNull DexoptResult result) {
+ switch (result.getReason()) {
+ case ReasonMapping.REASON_FIRST_BOOT:
+ case ReasonMapping.REASON_BOOT_AFTER_OTA:
+ case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
+ int numDexopted = 0;
+ int numSkipped = 0;
+ int numFailed = 0;
+ for (DexoptResult.PackageDexoptResult pkgRes :
+ result.getPackageDexoptResults()) {
+ switch (pkgRes.getStatus()) {
+ case DexoptResult.DEXOPT_PERFORMED:
+ numDexopted += 1;
+ break;
+ case DexoptResult.DEXOPT_SKIPPED:
+ numSkipped += 1;
+ break;
+ case DexoptResult.DEXOPT_FAILED:
+ numFailed += 1;
+ break;
+ }
+ }
+
+ reportBootDexopt(mBootDexoptStartTime, numDexopted, numSkipped, numFailed);
+ break;
+ }
+
for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
CompilerStats.PackageStats stats =
mPm.getOrCreateCompilerPackageStats(pkgRes.getPackageName());
@@ -932,6 +973,35 @@
mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
mPm.mCompilerStats.maybeWriteAsync();
}
+
+ if (result.getReason().equals(ReasonMapping.REASON_INACTIVE)) {
+ for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
+ if (pkgRes.getStatus() == DexoptResult.DEXOPT_PERFORMED) {
+ long pkgSizeBytes = 0;
+ long pkgSizeBeforeBytes = 0;
+ for (DexoptResult.DexContainerFileDexoptResult dexRes :
+ pkgRes.getDexContainerFileDexoptResults()) {
+ long dexContainerSize = new File(dexRes.getDexContainerFile()).length();
+ pkgSizeBytes += dexRes.getSizeBytes() + dexContainerSize;
+ pkgSizeBeforeBytes += dexRes.getSizeBeforeBytes() + dexContainerSize;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED,
+ pkgRes.getPackageName(), pkgSizeBeforeBytes, pkgSizeBytes,
+ false /* aggressive */);
+ }
+ }
+ }
+
+ var updatedPackages = new ArraySet<String>();
+ for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
+ if (pkgRes.hasUpdatedArtifacts()) {
+ updatedPackages.add(pkgRes.getPackageName());
+ }
+ }
+ if (!updatedPackages.isEmpty()) {
+ LocalServices.getService(PinnerService.class)
+ .update(updatedPackages, false /* force */);
+ }
}
}
@@ -953,12 +1023,9 @@
}
/**
- * Returns {@link ArtManagerLocal} if ART Service should be used for package dexopt.
+ * Returns the registered {@link ArtManagerLocal} instance, or else throws an unchecked error.
*/
- public static @Nullable ArtManagerLocal getArtManagerLocal() {
- if (!useArtService()) {
- return null;
- }
+ public static @NonNull ArtManagerLocal getArtManagerLocal() {
try {
return LocalManagerRegistry.getManagerOrThrow(ArtManagerLocal.class);
} catch (ManagerNotFoundException e) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a1e6c5f..a4c9baa 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -158,6 +158,8 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.DexoptParams;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
@@ -388,7 +390,8 @@
}
if (reconciledPkg.mCollectedSharedLibraryInfos != null
- || (oldPkgSetting != null && oldPkgSetting.getUsesLibraries() != null)) {
+ || (oldPkgSetting != null
+ && !oldPkgSetting.getSharedLibraryDependencies().isEmpty())) {
// Reconcile if the new package or the old package uses shared libraries.
// It is possible that the old package uses shared libraries but the new one doesn't.
mSharedLibraries.executeSharedLibrariesUpdate(pkg, pkgSetting, null, null,
@@ -1080,19 +1083,32 @@
"MinInstallableTargetSdk__min_installable_target_sdk",
0);
+ // Determine if enforcement is in strict mode
+ boolean strictMode = false;
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__install_block_strict_mode_enabled",
+ false)) {
+ if (parsedPackage.getTargetSdkVersion()
+ < DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__strict_mode_target_sdk",
+ 0)) {
+ strictMode = true;
+ }
+ }
+
// Skip enforcement when the bypass flag is set
boolean bypassLowTargetSdkBlock =
((installFlags & PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK) != 0);
// Skip enforcement for tests that were installed from adb
- if (!bypassLowTargetSdkBlock
+ if (!strictMode && !bypassLowTargetSdkBlock
&& ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0)) {
bypassLowTargetSdkBlock = true;
}
// Skip enforcement if the installer package name is not set
// (e.g. "pm install" from shell)
- if (!bypassLowTargetSdkBlock) {
+ if (!strictMode && !bypassLowTargetSdkBlock) {
if (request.getInstallerPackageName() == null) {
bypassLowTargetSdkBlock = true;
} else {
@@ -2384,6 +2400,7 @@
|| installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
| DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
| (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
DexoptOptions dexoptOptions =
@@ -2444,13 +2461,25 @@
realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
- // TODO(b/251903639): Call into ART Service.
- try {
- mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
- null /* instructionSets */, mPm.getOrCreateCompilerPackageStats(pkg),
- mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ if (useArtService()) {
+ PackageManagerLocal packageManagerLocal =
+ LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ packageManagerLocal.withFilteredSnapshot()) {
+ DexoptParams params =
+ dexoptOptions.convertToDexoptParams(0 /* extraFlags */);
+ DexOptHelper.getArtManagerLocal().dexoptPackage(
+ snapshot, packageName, params);
+ }
+ } else {
+ try {
+ mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
+ null /* instructionSets */,
+ mPm.getOrCreateCompilerPackageStats(pkg),
+ mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 490b2a9..767c0a7 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -168,13 +168,7 @@
Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
+ DexOptHelper.packagesToString(others));
for (PackageStateInternal pkg : others) {
- // TODO(b/251903639): Call into ART Service.
- try {
- mPackageManagerService.deleteOatArtifactsOfPackage(
- snapshot, pkg.getPackageName());
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
+ mPackageManagerService.deleteOatArtifactsOfPackage(snapshot, pkg.getPackageName());
}
}
long spaceAvailableNow = getAvailableSpace();
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 99fff72..4e75210 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -47,7 +47,6 @@
import android.util.ArraySet;
import android.util.SparseArray;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -708,12 +707,7 @@
@Override
@Deprecated
public final long deleteOatArtifactsOfPackage(String packageName) {
- // TODO(b/251903639): Call into ART Service.
- try {
- return mService.deleteOatArtifactsOfPackage(snapshot(), packageName);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
+ return mService.deleteOatArtifactsOfPackage(snapshot(), packageName);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e81fcba..94e96b0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -196,6 +196,7 @@
import com.android.server.Watchdog;
import com.android.server.apphibernation.AppHibernationManagerInternal;
import com.android.server.art.DexUseManagerLocal;
+import com.android.server.art.model.DeleteResult;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.Installer.InstallerException;
@@ -2949,15 +2950,6 @@
}
}
if (doTrim) {
- if (!isFirstBoot()) {
- try {
- ActivityManager.getService().showBootMessage(
- mContext.getResources().getString(
- R.string.android_upgrading_fstrim),
- true);
- } catch (RemoteException e) {
- }
- }
sm.runMaintenance();
}
} else {
@@ -2969,12 +2961,7 @@
}
public void updatePackagesIfNeeded() {
- // TODO(b/251903639): Call into ART Service.
- try {
- mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
+ mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
}
private void notifyPackageUseInternal(String packageName, int reason) {
@@ -5434,9 +5421,11 @@
String loadingPkgDexCodeIsa = InstructionSets.getDexCodeInstructionSet(
VMRuntime.getInstructionSet(loadingPkgAbi));
if (!loaderIsa.equals(loadingPkgDexCodeIsa)) {
- // TODO(b/251903639): Make this crash to surface this problem
- // better.
- Slog.w(PackageManagerService.TAG,
+ // TODO(b/251903639): We make this a wtf to surface any situations
+ // where this argument doesn't correspond to our expectations. Later
+ // it should be turned into an IllegalArgumentException, when we can
+ // assume it's the caller that's wrong rather than us.
+ Log.wtf(TAG,
"Invalid loaderIsa in notifyDexLoad call from "
+ loadingPackageName + ", uid " + callingUid
+ ": expected " + loadingPkgDexCodeIsa + ", got "
@@ -7096,17 +7085,39 @@
return AndroidPackageUtils.canHaveOatDir(packageState, packageState.getPkg());
}
- long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName)
- throws LegacyDexoptDisabledException {
+ long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName) {
PackageManagerServiceUtils.enforceSystemOrRootOrShell(
"Only the system or shell can delete oat artifacts");
- PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
- if (packageState == null || packageState.getPkg() == null) {
- return -1; // error code of deleteOptimizedFiles
+ if (DexOptHelper.useArtService()) {
+ // TODO(chiuwinson): Retrieve filtered snapshot from Computer instance instead.
+ try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ try {
+ DeleteResult res = DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
+ filteredSnapshot, packageName);
+ return res.getFreedBytes();
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.toString());
+ return -1;
+ } catch (IllegalStateException e) {
+ Slog.wtfStack(TAG, e.toString());
+ return -1;
+ }
+ }
+ } else {
+ PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
+ if (packageState == null || packageState.getPkg() == null) {
+ return -1; // error code of deleteOptimizedFiles
+ }
+ try {
+ return mDexManager.deleteOptimizedFiles(
+ ArtUtils.createArtPackageInfo(packageState.getPkg(), packageState));
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
}
- return mDexManager.deleteOptimizedFiles(
- ArtUtils.createArtPackageInfo(packageState.getPkg(), packageState));
}
List<String> getMimeGroupInternal(@NonNull Computer snapshot, String packageName,
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 53fdfaa..2a1172c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1219,7 +1219,7 @@
@NonNull
@Override
- public List<SharedLibrary> getUsesLibraries() {
+ public List<SharedLibrary> getSharedLibraryDependencies() {
return (List<SharedLibrary>) (List<?>) pkgState.getUsesLibraryInfos();
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index e5aaddb..10673c6 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -422,15 +422,18 @@
if (instructionSets == null) {
throw new IllegalStateException("instructionSet == null");
}
- String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
- for (String codePath : allCodePaths) {
- for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- // TODO(b/251903639): Call into ART Service.
- try {
- mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- } catch (Installer.InstallerException ignored) {
+ // TODO(b/265813358): ART Service currently doesn't support deleting optimized artifacts
+ // relative to an arbitrary APK path. Skip this and rely on its file GC instead.
+ if (!DexOptHelper.useArtService()) {
+ String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+ for (String codePath : allCodePaths) {
+ for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+ try {
+ mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ } catch (Installer.InstallerException ignored) {
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 53a5648..1c83880 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -35,6 +35,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
@@ -1480,36 +1481,45 @@
@Override
public void setUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("set user admin");
-
+ final long sessionId = logGrantAdminJourneyBegin(userId);
+ UserInfo info;
synchronized (mPackagesLock) {
- UserInfo info;
synchronized (mUsersLock) {
info = getUserInfoLU(userId);
}
if (info == null || info.isAdmin()) {
// Exit if no user found with that id, or the user is already an Admin.
+ logUserJourneyError(sessionId,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
+ userId);
return;
}
-
info.flags ^= UserInfo.FLAG_ADMIN;
writeUserLP(getUserDataLU(info.id));
}
+ logGrantAdminJourneyFinish(sessionId, userId, info.userType, info.flags);
}
@Override
public void revokeUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
+ final long sessionId = logRevokeAdminJourneyBegin(userId);
+ UserData user;
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
- UserData user = getUserDataLU(userId);
+ user = getUserDataLU(userId);
if (user == null || !user.info.isAdmin()) {
// Exit if no user found with that id, or the user is not an Admin.
+ logUserJourneyError(sessionId, FrameworkStatsLog
+ .USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
+ userId);
return;
}
user.info.flags ^= UserInfo.FLAG_ADMIN;
writeUserLP(user);
}
}
+ logRevokeAdminJourneyFinish(sessionId, userId, user.info.userType, user.info.flags);
}
/**
@@ -1769,28 +1779,6 @@
}
@Override
- public boolean isMediaSharedWithParent(@UserIdInt int userId) {
- checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
- "isMediaSharedWithParent");
- synchronized (mUsersLock) {
- UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
- return userTypeDetails != null ? userTypeDetails.isProfile()
- && userTypeDetails.isMediaSharedWithParent() : false;
- }
- }
-
- @Override
- public boolean isCredentialSharableWithParent(@UserIdInt int userId) {
- checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
- "isCredentialSharableWithParent");
- synchronized (mUsersLock) {
- UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
- return userTypeDetails != null && userTypeDetails.isProfile()
- && userTypeDetails.isCredentialSharableWithParent();
- }
- }
-
- @Override
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
"isUserUnlockingOrUnlocked");
@@ -2924,7 +2912,13 @@
final Intent broadcast = new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(broadcast, UserHandle.of(userId));
+ // Setting the MOST_RECENT policy allows us to discard older broadcasts
+ // still waiting to be delivered.
+ final Bundle options = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .toBundle();
+ mContext.sendBroadcastAsUser(broadcast, UserHandle.of(userId),
+ null /* receiverPermission */, options);
}
});
}
@@ -3718,14 +3712,12 @@
}
if (userVersion < 6) {
- final boolean splitSystemUser = UserManager.isSplitSystemUser();
synchronized (mUsersLock) {
for (int i = 0; i < mUsers.size(); i++) {
UserData userData = mUsers.valueAt(i);
- // In non-split mode, only user 0 can have restricted profiles
- if (!splitSystemUser && userData.info.isRestricted()
- && (userData.info.restrictedProfileParentId
- == UserInfo.NO_PROFILE_GROUP_ID)) {
+ // Only system user can have restricted profiles
+ if (userData.info.isRestricted() && (userData.info.restrictedProfileParentId
+ == UserInfo.NO_PROFILE_GROUP_ID)) {
userData.info.restrictedProfileParentId = UserHandle.USER_SYSTEM;
userIdsToWrite.add(userData.info.id);
}
@@ -5142,12 +5134,38 @@
userId, userType, flags, finish);
}
+ private long logGrantAdminJourneyBegin(@UserIdInt int userId) {
+ return logUserJourneyBegin(
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
+ userId);
+ }
+
+ private void logGrantAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
+ @UserInfoFlag int flags) {
+ logUserJourneyFinish(sessionId,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
+ userId, userType, flags, true);
+ }
+
+ private long logRevokeAdminJourneyBegin(@UserIdInt int userId) {
+ return logUserJourneyBegin(
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
+ userId);
+ }
+
+ private void logRevokeAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
+ @UserInfoFlag int flags) {
+ logUserJourneyFinish(sessionId,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
+ userId, userType, flags, true);
+ }
+
private void logUserJourneyFinish(long sessionId, int journey, @UserIdInt int userId,
String userType, @UserInfoFlag int flags, boolean finish) {
// log the journey atom with the user metadata
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
- journey, /* origin_user= */ -1, userId,
+ journey, /* origin_user= */ getCurrentUserId(), userId,
UserManager.getUserTypeForStatsd(userType), flags);
int event;
@@ -5158,6 +5176,12 @@
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
break;
+ case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
+ event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
+ break;
+ case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
+ event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
+ break;
default:
throw new IllegalArgumentException("Journey " + journey + " not expected.");
}
@@ -5179,6 +5203,12 @@
case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
break;
+ case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
+ event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
+ break;
+ case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
+ event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
+ break;
default:
throw new IllegalArgumentException("Journey " + journey + " not expected.");
}
@@ -5188,6 +5218,27 @@
return sessionId;
}
+ private void logUserJourneyError(long sessionId, int journey, @UserIdInt int userId) {
+
+ // log the journey atom with the user metadata
+ FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
+ journey, /* origin_user= */ getCurrentUserId(), userId);
+
+ int event;
+ switch (journey) {
+ case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
+ event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
+ break;
+ case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
+ event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
+ break;
+ default:
+ throw new IllegalArgumentException("Journey " + journey + " not expected.");
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
+ event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR);
+ }
+
/** Register callbacks for statsd pulled atoms. */
private void registerStatsCallbacks() {
final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -6496,7 +6547,6 @@
pw.println(" All guests ephemeral: " + Resources.getSystem().getBoolean(
com.android.internal.R.bool.config_guestUserEphemeral));
pw.println(" Force ephemeral users: " + mForceEphemeralUsers);
- pw.println(" Is split-system user: " + UserManager.isSplitSystemUser());
final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
pw.println(" Is headless-system mode: " + isHeadlessSystemUserMode);
if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index ddf3692..f86ee90 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -151,20 +151,6 @@
private final @Nullable int[] mDarkThemeBadgeColors;
/**
- * Denotes if the user shares media with its parent user.
- *
- * <p> Default value is false
- */
- private final boolean mIsMediaSharedWithParent;
-
- /**
- * Denotes if the user shares encryption credentials with its parent user.
- *
- * <p> Default value is false
- */
- private final boolean mIsCredentialSharableWithParent;
-
- /**
* The default {@link UserProperties} for the user type.
* <p> The uninitialized value of each property is implied by {@link UserProperties.Builder}.
*/
@@ -180,8 +166,6 @@
@Nullable Bundle defaultSystemSettings,
@Nullable Bundle defaultSecureSettings,
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
- boolean isMediaSharedWithParent,
- boolean isCredentialSharableWithParent,
@NonNull UserProperties defaultUserProperties) {
this.mName = name;
this.mEnabled = enabled;
@@ -201,8 +185,6 @@
this.mBadgeLabels = badgeLabels;
this.mBadgeColors = badgeColors;
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
- this.mIsMediaSharedWithParent = isMediaSharedWithParent;
- this.mIsCredentialSharableWithParent = isCredentialSharableWithParent;
this.mDefaultUserProperties = defaultUserProperties;
}
@@ -309,21 +291,6 @@
return mDarkThemeBadgeColors[Math.min(badgeIndex, mDarkThemeBadgeColors.length - 1)];
}
- /**
- * Returns true if the user has shared media with parent user or false otherwise.
- */
- public boolean isMediaSharedWithParent() {
- return mIsMediaSharedWithParent;
- }
-
- /**
- * Returns true if the user has shared encryption credential with parent user or
- * false otherwise.
- */
- public boolean isCredentialSharableWithParent() {
- return mIsCredentialSharableWithParent;
- }
-
/**
* Returns the reference to the default {@link UserProperties} for this type of user.
@@ -437,8 +404,6 @@
private @DrawableRes int mIconBadge = Resources.ID_NULL;
private @DrawableRes int mBadgePlain = Resources.ID_NULL;
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
- private boolean mIsMediaSharedWithParent = false;
- private boolean mIsCredentialSharableWithParent = false;
// Default UserProperties cannot be null but for efficiency we don't initialize it now.
// If it isn't set explicitly, {@link UserProperties.Builder#build()} will be used.
private @Nullable UserProperties mDefaultUserProperties = null;
@@ -533,24 +498,6 @@
}
/**
- * Sets shared media property for the user.
- * @param isMediaSharedWithParent the value to be set, true or false
- */
- public Builder setIsMediaSharedWithParent(boolean isMediaSharedWithParent) {
- mIsMediaSharedWithParent = isMediaSharedWithParent;
- return this;
- }
-
- /**
- * Sets shared media property for the user.
- * @param isCredentialSharableWithParent the value to be set, true or false
- */
- public Builder setIsCredentialSharableWithParent(boolean isCredentialSharableWithParent) {
- mIsCredentialSharableWithParent = isCredentialSharableWithParent;
- return this;
- }
-
- /**
* Sets (replacing if necessary) the default UserProperties object for this user type.
* Takes a builder, rather than a built object, to efficiently ensure that a fresh copy of
* properties is stored (since it later might be modified by UserProperties#updateFromXml).
@@ -609,8 +556,6 @@
mDefaultSystemSettings,
mDefaultSecureSettings,
mDefaultCrossProfileIntentFilters,
- mIsMediaSharedWithParent,
- mIsCredentialSharableWithParent,
getDefaultUserProperties());
}
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index edb2a4be3b..d56ee30 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -122,8 +122,6 @@
.setMaxAllowedPerParent(1)
.setLabel(0)
.setDefaultRestrictions(null)
- .setIsMediaSharedWithParent(true)
- .setIsCredentialSharableWithParent(true)
.setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
@@ -135,7 +133,10 @@
.setCrossProfileIntentFilterAccessControl(
UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
.setCrossProfileIntentResolutionStrategy(UserProperties
- .CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING));
+ .CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING)
+ .setIsMediaSharedWithParent(true)
+ .setIsCredentialSharableWithParent(true)
+ );
}
/**
@@ -167,11 +168,11 @@
.setDefaultRestrictions(getDefaultManagedProfileRestrictions())
.setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
.setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
- .setIsCredentialSharableWithParent(true)
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
- .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE));
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+ .setIsCredentialSharableWithParent(true));
}
/**
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index b4cedfb..d8b6cd5 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -50,11 +50,15 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.pm.DexOptHelper;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
+import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceCompilerMapping;
+import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
@@ -210,20 +214,15 @@
Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
}
- // TODO(b/251903639): Call into ART Service.
- try {
- if (bootImageProfile) {
- snapshotBootImageProfile(callback);
- } else {
- snapshotAppProfile(packageName, codePath, callback);
- }
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ if (bootImageProfile) {
+ snapshotBootImageProfile(callback);
+ } else {
+ snapshotAppProfile(packageName, codePath, callback);
}
}
- private void snapshotAppProfile(String packageName, String codePath,
- ISnapshotRuntimeProfileCallback callback) throws LegacyDexoptDisabledException {
+ private void snapshotAppProfile(
+ String packageName, String codePath, ISnapshotRuntimeProfileCallback callback) {
PackageInfo info = null;
try {
// Note that we use the default user 0 to retrieve the package info.
@@ -260,17 +259,45 @@
}
// All good, create the profile snapshot.
- int appId = UserHandle.getAppId(info.applicationInfo.uid);
- if (appId < 0) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
- return;
- }
+ if (DexOptHelper.useArtService()) {
+ ParcelFileDescriptor fd;
- createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
- appId, callback);
- // Destroy the snapshot, we no longer need it.
- destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ fd = DexOptHelper.getArtManagerLocal().snapshotAppProfile(
+ snapshot, packageName, splitName);
+ } catch (IllegalArgumentException e) {
+ // ArtManagerLocal.snapshotAppProfile couldn't find the package or split. Since
+ // we've checked them above this can only happen due to race, i.e. the package got
+ // removed. So let's report it as SNAPSHOT_FAILED_PACKAGE_NOT_FOUND even if it was
+ // for the split.
+ // TODO(mast): Reuse the same snapshot to avoid this race.
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
+ return;
+ } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ return;
+ }
+
+ postSuccess(packageName, fd, callback);
+ } else {
+ int appId = UserHandle.getAppId(info.applicationInfo.uid);
+ if (appId < 0) {
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
+ return;
+ }
+
+ try {
+ createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
+ appId, callback);
+ // Destroy the snapshot, we no longer need it.
+ destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
private void createProfileSnapshot(String packageName, String profileName, String classpath,
@@ -340,23 +367,43 @@
}
}
- private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback)
- throws LegacyDexoptDisabledException {
- // Combine the profiles for boot classpath and system server classpath.
- // This avoids having yet another type of profiles and simplifies the processing.
- String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
- Os.getenv("SYSTEMSERVERCLASSPATH"));
+ private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
+ if (DexOptHelper.useArtService()) {
+ ParcelFileDescriptor fd;
- final String standaloneSystemServerJars = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
- if (standaloneSystemServerJars != null) {
- classpath = String.join(":", classpath, standaloneSystemServerJars);
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ fd = DexOptHelper.getArtManagerLocal().snapshotBootImageProfile(snapshot);
+ } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
+ postError(callback, BOOT_IMAGE_ANDROID_PACKAGE,
+ ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ return;
+ }
+
+ postSuccess(BOOT_IMAGE_ANDROID_PACKAGE, fd, callback);
+ } else {
+ // Combine the profiles for boot classpath and system server classpath.
+ // This avoids having yet another type of profiles and simplifies the processing.
+ String classpath = String.join(
+ ":", Os.getenv("BOOTCLASSPATH"), Os.getenv("SYSTEMSERVERCLASSPATH"));
+
+ final String standaloneSystemServerJars = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
+ if (standaloneSystemServerJars != null) {
+ classpath = String.join(":", classpath, standaloneSystemServerJars);
+ }
+
+ try {
+ // Create the snapshot.
+ createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME,
+ classpath,
+ /*appId*/ -1, callback);
+ // Destroy the snapshot, we no longer need it.
+ destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
}
-
- // Create the snapshot.
- createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME, classpath,
- /*appId*/ -1, callback);
- // Destroy the snapshot, we no longer need it.
- destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
}
/**
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index 4fce5e8..310c0e8 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -21,18 +21,19 @@
import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.util.Log;
import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
-import com.android.server.pm.DexOptHelper;
import com.android.server.pm.PackageManagerService;
/**
* Options used for dexopt invocations.
*/
public final class DexoptOptions {
+ private static final String TAG = "DexoptOptions";
+
// When set, the profiles will be checked for updates before calling dexopt. If
// the apps profiles didn't update in a meaningful way (decided by the compiler), dexopt
// will be skipped.
@@ -88,8 +89,9 @@
// The set of flags for the dexopt options. It's a mix of the DEXOPT_* flags.
private final int mFlags;
- // When not null, dexopt will optimize only the split identified by this name.
- // It only applies for primary apk and it's always null if mOnlySecondaryDex is true.
+ // When not null, dexopt will optimize only the split identified by this APK file name (not
+ // split name). It only applies for primary apk and it's always null if mOnlySecondaryDex is
+ // true.
private final String mSplitName;
// The reason for invoking dexopt (see PackageManagerService.REASON_* constants).
@@ -250,14 +252,20 @@
*
* @param extraFlags extra {@link ArtFlags#DexoptFlags} to set in the returned
* {@code DexoptParams} beyond those converted from this object
- * @return null if the settings cannot be accurately represented, and hence the old
- * PackageManager/installd code paths need to be used.
+ * @throws UnsupportedOperationException if the settings cannot be accurately represented.
*/
- public @Nullable DexoptParams convertToDexoptParams(/*@DexoptFlags*/ int extraFlags) {
+ public @NonNull DexoptParams convertToDexoptParams(/*@DexoptFlags*/ int extraFlags) {
if (mSplitName != null) {
- DexOptHelper.reportArtManagerFallback(
- mPackageName, "Request to optimize only split " + mSplitName);
- return null;
+ // ART Service supports dexopting a single split - see ArtFlags.FLAG_FOR_SINGLE_SPLIT.
+ // However using it here requires searching through the splits to find the one matching
+ // the APK file name in mSplitName, and we don't have the AndroidPackage available for
+ // that.
+ //
+ // Hence we throw here instead, under the assumption that no code paths that dexopt
+ // splits need this conversion (e.g. shell commands with the --split argument are
+ // handled by ART Service directly).
+ throw new UnsupportedOperationException(
+ "Request to optimize only split " + mSplitName + " for " + mPackageName);
}
/*@DexoptFlags*/ int flags = extraFlags;
@@ -280,11 +288,11 @@
flags |= ArtFlags.FLAG_SHOULD_DOWNGRADE;
}
if ((mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) == 0) {
- // ART Service cannot be instructed to ignore a DM file if present, so not setting this
- // flag is not supported.
- DexOptHelper.reportArtManagerFallback(
- mPackageName, "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set");
- return null;
+ // ART Service cannot be instructed to ignore a DM file if present.
+ Log.w(TAG,
+ "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set in request to optimise "
+ + mPackageName
+ + " - ART Service will unconditionally use a DM file if present.");
}
/*@PriorityClassApi*/ int priority;
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index a12c9d0..106b149 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -158,10 +158,11 @@
PackageUserState getStateForUser(@NonNull UserHandle user);
/**
- * @see R.styleable#AndroidManifestUsesLibrary
+ * List of shared libraries that this package declares a dependency on. This includes all
+ * types of libraries, system or app provided and Java or native.
*/
@NonNull
- List<SharedLibrary> getUsesLibraries();
+ List<SharedLibrary> getSharedLibraryDependencies();
/** Whether this represents an APEX module. This is different from an APK inside an APEX. */
boolean isApex();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index bc6dab4..91a25db3 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -190,7 +190,7 @@
mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
- mUsesLibraries = Collections.unmodifiableList(pkgState.getUsesLibraries());
+ mUsesLibraries = Collections.unmodifiableList(pkgState.getSharedLibraryDependencies());
mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride());
setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled());
@@ -693,7 +693,7 @@
}
@DataClass.Generated.Member
- public @NonNull List<SharedLibrary> getUsesLibraries() {
+ public @NonNull List<SharedLibrary> getSharedLibraryDependencies() {
return mUsesLibraries;
}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 91bb677..7f733ef 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -27,6 +27,7 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Environment;
+import android.os.PowerManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -80,7 +81,8 @@
* provided.
*/
public final class DeviceStateProviderImpl implements DeviceStateProvider,
- InputManagerInternal.LidSwitchCallback, SensorEventListener {
+ InputManagerInternal.LidSwitchCallback, SensorEventListener,
+ PowerManager.OnThermalStatusChangedListener {
private static final String TAG = "DeviceStateProviderImpl";
private static final boolean DEBUG = false;
@@ -97,6 +99,10 @@
private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
+ private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+ "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
+ private static final String FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL =
+ "FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL";
/** Interface that allows reading the device state configuration. */
interface ReadableConfig {
@@ -152,6 +158,13 @@
break;
case FLAG_EMULATED_ONLY:
flags |= DeviceState.FLAG_EMULATED_ONLY;
+ break;
+ case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
+ flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+ break;
+ case FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL:
+ flags |= DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL;
+ break;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
+ configFlagString);
@@ -194,6 +207,8 @@
private Boolean mIsLidOpen;
@GuardedBy("mLock")
private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE;
private DeviceStateProviderImpl(@NonNull Context context,
@NonNull List<DeviceState> deviceStates,
@@ -208,6 +223,16 @@
mOrderedStates = orderedStates;
setStateConditions(deviceStates, stateConditions);
+
+ // If any of the device states are thermal sensitive, i.e. it should be disabled when the
+ // device is overheating, then we will update the list of supported states when thermal
+ // status changes.
+ if (hasThermalSensitiveState(deviceStates)) {
+ PowerManager powerManager = context.getSystemService(PowerManager.class);
+ if (powerManager != null) {
+ powerManager.addThermalStatusListener(this);
+ }
+ }
}
private void setStateConditions(@NonNull List<DeviceState> deviceStates,
@@ -347,16 +372,25 @@
/** Notifies the listener that the set of supported device states has changed. */
private void notifySupportedStatesChanged() {
- DeviceState[] supportedStates;
+ List<DeviceState> supportedStates = new ArrayList<>();
+ Listener listener;
synchronized (mLock) {
if (mListener == null) {
return;
}
-
- supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length);
+ listener = mListener;
+ for (DeviceState deviceState : mOrderedStates) {
+ if (isThermalStatusCriticalOrAbove(mThermalStatus)
+ && deviceState.hasFlag(
+ DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+ continue;
+ }
+ supportedStates.add(deviceState);
+ }
}
- mListener.onSupportedDeviceStatesChanged(supportedStates);
+ listener.onSupportedDeviceStatesChanged(
+ supportedStates.toArray(new DeviceState[supportedStates.size()]));
}
/** Computes the current device state and notifies the listener of a change, if needed. */
@@ -639,4 +673,43 @@
return new FileInputStream(mFile);
}
}
+
+ @Override
+ public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) {
+ int previousThermalStatus;
+ synchronized (mLock) {
+ previousThermalStatus = mThermalStatus;
+ mThermalStatus = thermalStatus;
+ }
+
+ boolean isThermalStatusCriticalOrAbove = isThermalStatusCriticalOrAbove(thermalStatus);
+ boolean isPreviousThermalStatusCriticalOrAbove =
+ isThermalStatusCriticalOrAbove(previousThermalStatus);
+ if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
+ Slog.i(TAG, "Updating supported device states due to thermal status change."
+ + " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
+ notifySupportedStatesChanged();
+ }
+ }
+
+ private static boolean isThermalStatusCriticalOrAbove(
+ @PowerManager.ThermalStatus int thermalStatus) {
+ switch (thermalStatus) {
+ case PowerManager.THERMAL_STATUS_CRITICAL:
+ case PowerManager.THERMAL_STATUS_EMERGENCY:
+ case PowerManager.THERMAL_STATUS_SHUTDOWN:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) {
+ for (DeviceState state : deviceStates) {
+ if (state.hasFlag(DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2f72741..5a0c344 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -575,6 +575,9 @@
// What we do when the user double-taps on home
private int mDoubleTapOnHomeBehavior;
+ // Whether to lock the device after the next app transition has finished.
+ private boolean mLockAfterAppTransitionFinished;
+
// Allowed theater mode wake actions
private boolean mAllowTheaterModeWakeFromKey;
private boolean mAllowTheaterModeWakeFromPowerKey;
@@ -661,7 +664,7 @@
dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj);
break;
case MSG_DISPATCH_SHOW_RECENTS:
- showRecents();
+ showRecentApps(false);
break;
case MSG_DISPATCH_SHOW_GLOBAL_ACTIONS:
showGlobalActionsInternal();
@@ -717,7 +720,7 @@
handleRingerChordGesture();
break;
case MSG_SCREENSHOT_CHORD:
- handleScreenShot(msg.arg1, msg.arg2);
+ handleScreenShot(msg.arg1);
break;
}
}
@@ -1073,11 +1076,10 @@
return;
}
- // Make sure the device locks. Unfortunately, this has the side-effect of briefly revealing
- // the lock screen before the dream appears. Note that locking is a side-effect of the no
- // dream action that is executed if we early return above.
- // TODO(b/261662912): Find a better way to lock the device that doesn't result in jank.
- lockNow(null);
+ synchronized (mLock) {
+ // Lock the device after the dream transition has finished.
+ mLockAfterAppTransitionFinished = true;
+ }
dreamManagerInternal.requestDream();
}
@@ -1518,9 +1520,9 @@
|| mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
}
- private void interceptScreenshotChord(int type, int source, long pressDelay) {
+ private void interceptScreenshotChord(int source, long pressDelay) {
mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, type, source),
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source),
pressDelay);
}
@@ -1590,9 +1592,8 @@
}
};
- private void handleScreenShot(@WindowManager.ScreenshotType int type,
- @WindowManager.ScreenshotSource int source) {
- mDefaultDisplayPolicy.takeScreenshot(type, source);
+ private void handleScreenShot(@WindowManager.ScreenshotSource int source) {
+ mDefaultDisplayPolicy.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source);
}
@Override
@@ -2197,6 +2198,22 @@
handleTransitionForKeyguardLw(
keyguardGoingAwayCancelled /* startKeyguardExitAnimation */,
true /* notifyOccluded */);
+
+ synchronized (mLock) {
+ mLockAfterAppTransitionFinished = false;
+ }
+ }
+
+ @Override
+ public void onAppTransitionFinishedLocked(IBinder token) {
+ synchronized (mLock) {
+ if (!mLockAfterAppTransitionFinished) {
+ return;
+ }
+ mLockAfterAppTransitionFinished = false;
+ }
+
+ lockNow(null);
}
});
@@ -2228,7 +2245,7 @@
@Override
void execute() {
mPowerKeyHandled = true;
- interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN,
+ interceptScreenshotChord(
SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
}
@Override
@@ -2910,7 +2927,7 @@
break;
case KeyEvent.KEYCODE_RECENT_APPS:
if (down && repeatCount == 0) {
- showRecents();
+ showRecentApps(false /* triggeredFromAltTab */);
}
return key_consumed;
case KeyEvent.KEYCODE_APP_SWITCH:
@@ -2956,8 +2973,7 @@
break;
case KeyEvent.KEYCODE_S:
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
- interceptScreenshotChord(
- TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
return key_consumed;
}
break;
@@ -3094,23 +3110,22 @@
}
break;
case KeyEvent.KEYCODE_TAB:
- if (down) {
- if (event.isMetaPressed()) {
- if (!keyguardOn && isUserSetupComplete()) {
- showRecents();
+ if (down && event.isMetaPressed()) {
+ if (!keyguardOn && isUserSetupComplete()) {
+ showRecentApps(false);
+ return key_consumed;
+ }
+ } else if (down && repeatCount == 0) {
+ // Display task switcher for ALT-TAB.
+ if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
+ final int shiftlessModifiers =
+ event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
+ if (KeyEvent.metaStateHasModifiers(
+ shiftlessModifiers, KeyEvent.META_ALT_ON)) {
+ mRecentAppsHeldModifiers = shiftlessModifiers;
+ showRecentApps(true);
return key_consumed;
}
- } else {
- // Display task switcher for ALT-TAB.
- if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
- final int modifiers = event.getModifiers();
- if (KeyEvent.metaStateHasModifiers(modifiers, KeyEvent.META_ALT_ON)) {
- mRecentAppsHeldModifiers = modifiers;
- showRecentsFromAltTab(KeyEvent.metaStateHasModifiers(modifiers,
- KeyEvent.META_SHIFT_ON));
- return key_consumed;
- }
- }
}
}
break;
@@ -3402,8 +3417,7 @@
break;
case KeyEvent.KEYCODE_SYSRQ:
if (down && repeatCount == 0) {
- interceptScreenshotChord(
- TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
}
return true;
}
@@ -3647,19 +3661,11 @@
mHandler.obtainMessage(MSG_DISPATCH_SHOW_RECENTS).sendToTarget();
}
- private void showRecents() {
+ private void showRecentApps(boolean triggeredFromAltTab) {
mPreloadedRecentApps = false; // preloading no longer needs to be canceled
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
- statusbar.showRecentApps(false /* triggeredFromAltTab */, false /* forward */);
- }
- }
-
- private void showRecentsFromAltTab(boolean forward) {
- mPreloadedRecentApps = false; // preloading no longer needs to be canceled
- StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
- if (statusbar != null) {
- statusbar.showRecentApps(true /* triggeredFromAltTab */, forward);
+ statusbar.showRecentApps(triggeredFromAltTab);
}
}
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index 79e35c2..54f3476 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -20,8 +20,10 @@
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
import android.content.Context;
+import android.os.Handler;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -32,6 +34,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IntPair;
import java.util.Arrays;
@@ -43,12 +46,16 @@
* Stores stats about CPU wakeups and tries to attribute them to subsystems and uids.
*/
public class CpuWakeupStats {
+ private static final String TAG = "CpuWakeupStats";
+
private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
@VisibleForTesting
static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days.
@VisibleForTesting
static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
+ private static final long WAKEUP_WRITE_DELAY_MS = 2 * 60 * 1000; // 2 minutes.
+ private final Handler mHandler;
private final IrqDeviceMap mIrqDeviceMap;
private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
@@ -58,8 +65,58 @@
final TimeSparseArray<SparseArray<SparseBooleanArray>> mWakeupAttribution =
new TimeSparseArray<>();
- public CpuWakeupStats(Context context, int mapRes) {
+ public CpuWakeupStats(Context context, int mapRes, Handler handler) {
mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
+ mHandler = handler;
+ }
+
+ private static int subsystemToStatsReason(int subsystem) {
+ switch (subsystem) {
+ case CPU_WAKEUP_SUBSYSTEM_ALARM:
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__ALARM;
+ }
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN;
+ }
+
+ private synchronized void logWakeupToStatsLog(Wakeup wakeupToLog) {
+ if (ArrayUtils.isEmpty(wakeupToLog.mDevices)) {
+ FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
+ FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN,
+ FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN,
+ null,
+ wakeupToLog.mElapsedMillis);
+ return;
+ }
+
+ final SparseArray<SparseBooleanArray> wakeupAttribution = mWakeupAttribution.get(
+ wakeupToLog.mElapsedMillis);
+ if (wakeupAttribution == null) {
+ // This is not expected but can theoretically happen in extreme situations, e.g. if we
+ // remove the wakeup before the handler gets to process this message.
+ Slog.wtf(TAG, "Unexpected null attribution found for " + wakeupToLog);
+ return;
+ }
+ for (int i = 0; i < wakeupAttribution.size(); i++) {
+ final int subsystem = wakeupAttribution.keyAt(i);
+ final SparseBooleanArray uidMap = wakeupAttribution.valueAt(i);
+ final int[] uids;
+ if (uidMap == null || uidMap.size() == 0) {
+ uids = new int[0];
+ } else {
+ final IntArray tmp = new IntArray(uidMap.size());
+ for (int j = 0; j < uidMap.size(); j++) {
+ if (uidMap.valueAt(j)) {
+ tmp.add(uidMap.keyAt(j));
+ }
+ }
+ uids = tmp.toArray();
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
+ FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ,
+ subsystemToStatsReason(subsystem),
+ uids,
+ wakeupToLog.mElapsedMillis);
+ }
}
/** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
@@ -83,6 +140,7 @@
for (int i = lastIdx; i >= 0; i--) {
mWakeupAttribution.removeAt(i);
}
+ mHandler.postDelayed(() -> logWakeupToStatsLog(parsedWakeup), WAKEUP_WRITE_DELAY_MS);
}
/** Notes a waking activity that could have potentially woken up the CPU. */
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index fd64c75..7beb1ed 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -36,12 +36,14 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.SystemConfig;
import com.android.server.pm.ApexManager;
import java.io.BufferedReader;
@@ -358,6 +360,13 @@
private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
@FailureReasons int rollbackReason) {
assertInWorkerThread();
+
+ if (isAutomaticRollbackDenied(SystemConfig.getInstance(), failedPackage)) {
+ Slog.d(TAG, "Automatic rollback not allowed for package "
+ + failedPackage.getPackageName());
+ return;
+ }
+
final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
final String failedPackageToLog;
@@ -420,6 +429,17 @@
}
/**
+ * Returns true if this package is not eligible for automatic rollback.
+ */
+ @VisibleForTesting
+ @AnyThread
+ public static boolean isAutomaticRollbackDenied(SystemConfig systemConfig,
+ VersionedPackage versionedPackage) {
+ return systemConfig.getAutomaticRollbackDenylistedPackages()
+ .contains(versionedPackage.getPackageName());
+ }
+
+ /**
* Two-phase rollback:
* 1. roll back rebootless apexes first
* 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index f973f5c..8068c6f 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -249,7 +249,7 @@
targetDir.mkdirs();
File targetFile = new File(targetDir, sourceFile.getName());
- boolean fallbackToCopy = !isLinkPossible(sourceFile, targetFile);
+ boolean fallbackToCopy = !isLinkPossible(sourceFile, targetDir);
if (!fallbackToCopy) {
try {
// Create a hard link to avoid copy
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 411b2fa..e148a48 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -72,8 +72,7 @@
new Intent(RecognitionService.SERVICE_INTERFACE).setComponent(serviceName),
Context.BIND_AUTO_CREATE
| Context.BIND_FOREGROUND_SERVICE
- | Context.BIND_INCLUDE_CAPABILITIES
- | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
+ | Context.BIND_INCLUDE_CAPABILITIES,
userId,
IRecognitionService.Stub::asInterface);
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 6c616e0..b3e915a 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -170,6 +170,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.IProcessStats;
import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.app.procstats.StatsEventOutput;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelAllocationStats;
@@ -613,12 +614,19 @@
}
case FrameworkStatsLog.PROC_STATS:
synchronized (mProcStatsLock) {
- return pullProcStatsLocked(ProcessStats.REPORT_ALL, atomTag, data);
+ return pullProcStatsLocked(atomTag, data);
}
case FrameworkStatsLog.PROC_STATS_PKG_PROC:
synchronized (mProcStatsLock) {
- return pullProcStatsLocked(ProcessStats.REPORT_PKG_PROC_STATS, atomTag,
- data);
+ return pullProcStatsLocked(atomTag, data);
+ }
+ case FrameworkStatsLog.PROCESS_STATE:
+ synchronized (mProcStatsLock) {
+ return pullProcessStateLocked(atomTag, data);
+ }
+ case FrameworkStatsLog.PROCESS_ASSOCIATION:
+ synchronized (mProcStatsLock) {
+ return pullProcessAssociationLocked(atomTag, data);
}
case FrameworkStatsLog.DISK_IO:
synchronized (mDiskIoLock) {
@@ -891,6 +899,8 @@
registerNumFacesEnrolled();
registerProcStats();
registerProcStatsPkgProc();
+ registerProcessState();
+ registerProcessAssociation();
registerDiskIO();
registerPowerProfile();
registerProcessCpuTime();
@@ -2884,59 +2894,138 @@
);
}
- private int pullProcStatsLocked(int section, int atomTag, List<StatsEvent> pulledData) {
+ private void registerProcessState() {
+ int tagId = FrameworkStatsLog.PROCESS_STATE;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl);
+ }
+
+ private void registerProcessAssociation() {
+ int tagId = FrameworkStatsLog.PROCESS_ASSOCIATION;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl);
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private ProcessStats getStatsFromProcessStatsService(int atomTag) {
IProcessStats processStatsService = getIProcessStatsService();
if (processStatsService == null) {
- return StatsManager.PULL_SKIP;
+ return null;
}
-
final long token = Binder.clearCallingIdentity();
try {
// force procstats to flush & combine old files into one store
- long lastHighWaterMark = readProcStatsHighWaterMark(section);
-
- ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
- for (int i = 0; i < protoStreams.length; i++) {
- protoStreams[i] = new ProtoOutputStream();
- }
-
+ long lastHighWaterMark = readProcStatsHighWaterMark(atomTag);
ProcessStats procStats = new ProcessStats(false);
// Force processStatsService to aggregate all in-storage and in-memory data.
- long highWaterMark = processStatsService.getCommittedStatsMerged(
- lastHighWaterMark, section, true, null, procStats);
- procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
-
- for (int i = 0; i < protoStreams.length; i++) {
- byte[] bytes = protoStreams[i].getBytes(); // cache the value
- if (bytes.length > 0) {
- pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, bytes,
- // This is a shard ID, and is specified in the metric definition to be
- // a dimension. This will result in statsd using RANDOM_ONE_SAMPLE to
- // keep all the shards, as it thinks each shard is a different dimension
- // of data.
- i));
- }
- }
-
- new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + lastHighWaterMark)
+ long highWaterMark =
+ processStatsService.getCommittedStatsMerged(
+ lastHighWaterMark,
+ ProcessStats.REPORT_ALL, // ignored since committedStats below is null.
+ true,
+ null, // committedStats
+ procStats);
+ new File(
+ mBaseDir.getAbsolutePath()
+ + "/"
+ + highWaterMarkFilePrefix(atomTag)
+ + "_"
+ + lastHighWaterMark)
.delete();
- new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + highWaterMark)
+ new File(
+ mBaseDir.getAbsolutePath()
+ + "/"
+ + highWaterMarkFilePrefix(atomTag)
+ + "_"
+ + highWaterMark)
.createNewFile();
+ return procStats;
} catch (RemoteException | IOException e) {
Slog.e(TAG, "Getting procstats failed: ", e);
- return StatsManager.PULL_SKIP;
+ return null;
} finally {
Binder.restoreCallingIdentity(token);
}
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private int pullProcStatsLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
+ for (int i = 0; i < protoStreams.length; i++) {
+ protoStreams[i] = new ProtoOutputStream();
+ }
+ procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
+ for (int i = 0; i < protoStreams.length; i++) {
+ byte[] bytes = protoStreams[i].getBytes(); // cache the value
+ if (bytes.length > 0) {
+ pulledData.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ bytes,
+ // This is a shard ID, and is specified in the metric definition to
+ // be
+ // a dimension. This will result in statsd using RANDOM_ONE_SAMPLE
+ // to
+ // keep all the shards, as it thinks each shard is a different
+ // dimension
+ // of data.
+ i));
+ }
+ }
return StatsManager.PULL_SUCCESS;
}
+ @GuardedBy("mProcStatsLock")
+ private int pullProcessStateLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ procStats.dumpProcessState(atomTag, new StatsEventOutput(pulledData));
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private int pullProcessAssociationLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ procStats.dumpProcessAssociation(atomTag, new StatsEventOutput(pulledData));
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private String highWaterMarkFilePrefix(int atomTag) {
+ // For backward compatibility, use the legacy ProcessStats enum value as the prefix for
+ // PROC_STATS and PROC_STATS_PKG_PROC.
+ if (atomTag == FrameworkStatsLog.PROC_STATS) {
+ return String.valueOf(ProcessStats.REPORT_ALL);
+ }
+ if (atomTag == FrameworkStatsLog.PROC_STATS_PKG_PROC) {
+ return String.valueOf(ProcessStats.REPORT_PKG_PROC_STATS);
+ }
+ return "atom-" + atomTag;
+ }
+
// read high watermark for section
- private long readProcStatsHighWaterMark(int section) {
+ private long readProcStatsHighWaterMark(int atomTag) {
try {
- File[] files = mBaseDir.listFiles((d, name) -> {
- return name.toLowerCase().startsWith(String.valueOf(section) + '_');
- });
+ File[] files =
+ mBaseDir.listFiles(
+ (d, name) -> {
+ return name.toLowerCase()
+ .startsWith(highWaterMarkFilePrefix(atomTag) + '_');
+ });
if (files == null || files.length == 0) {
return 0;
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 05b3ce7..5521384 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -39,7 +39,7 @@
void cancelPreloadRecentApps();
- void showRecentApps(boolean triggeredFromAltTab, boolean forward);
+ void showRecentApps(boolean triggeredFromAltTab);
void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 0f49981..83f4805 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -454,10 +454,10 @@
}
@Override
- public void showRecentApps(boolean triggeredFromAltTab, boolean forward) {
+ public void showRecentApps(boolean triggeredFromAltTab) {
if (mBar != null) {
try {
- mBar.showRecentApps(triggeredFromAltTab, forward);
+ mBar.showRecentApps(triggeredFromAltTab);
} catch (RemoteException ex) {}
}
}
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index ca4a32f..099c9ae 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -16,9 +16,6 @@
package com.android.server.vcn;
-import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
-import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
-import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
@@ -48,7 +45,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.vcn.util.PersistableBundleUtils;
import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import java.util.ArrayList;
@@ -109,6 +105,12 @@
@NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot;
+ @NonNull
+ private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
+ (int logicalSlotIndex, int subscriptionId, int carrierId, int specificCarrierId) ->
+ handleActionCarrierConfigChanged(logicalSlotIndex, subscriptionId);
+
+
public TelephonySubscriptionTracker(
@NonNull Context context,
@NonNull Handler handler,
@@ -149,13 +151,14 @@
public void register() {
final HandlerExecutor executor = new HandlerExecutor(mHandler);
final IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
filter.addAction(ACTION_MULTI_SIM_CONFIG_CHANGED);
mContext.registerReceiver(this, filter, null, mHandler);
mSubscriptionManager.addOnSubscriptionsChangedListener(
executor, mSubscriptionChangedListener);
mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener);
+ mCarrierConfigManager.registerCarrierConfigChangeListener(executor,
+ mCarrierConfigChangeListener);
registerCarrierPrivilegesCallbacks();
}
@@ -197,6 +200,7 @@
mContext.unregisterReceiver(this);
mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener);
+ mCarrierConfigManager.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
unregisterCarrierPrivilegesCallbacks();
}
@@ -273,7 +277,7 @@
}
/**
- * Broadcast receiver for ACTION_CARRIER_CONFIG_CHANGED
+ * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
*
* <p>The broadcast receiver is registered with mHandler, so callbacks & broadcasts are all
* serialized on mHandler, avoiding the need for locking.
@@ -281,9 +285,6 @@
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
- case ACTION_CARRIER_CONFIG_CHANGED:
- handleActionCarrierConfigChanged(context, intent);
- break;
case ACTION_MULTI_SIM_CONFIG_CHANGED:
handleActionMultiSimConfigChanged(context, intent);
break;
@@ -310,26 +311,21 @@
handleSubscriptionsChanged();
}
- private void handleActionCarrierConfigChanged(Context context, Intent intent) {
- // Accept sticky broadcasts; if CARRIER_CONFIG_CHANGED was previously broadcast and it
- // already was for an identified carrier, we can stop waiting for initial load to complete
- final int subId = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID);
- final int slotId = intent.getIntExtra(EXTRA_SLOT_INDEX, INVALID_SIM_SLOT_INDEX);
-
+ private void handleActionCarrierConfigChanged(int slotId, int subId) {
if (slotId == INVALID_SIM_SLOT_INDEX) {
return;
}
if (SubscriptionManager.isValidSubscriptionId(subId)) {
- final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId);
+ // Get only configs as needed to save memory.
+ final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId,
+ VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
mReadySubIdsBySlotId.put(slotId, subId);
- final PersistableBundle minimized =
- PersistableBundleUtils.minimizeBundle(
- carrierConfig, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
- if (minimized != null) {
- mSubIdToCarrierConfigMap.put(subId, new PersistableBundleWrapper(minimized));
+ if (!carrierConfig.isEmpty()) {
+ mSubIdToCarrierConfigMap.put(subId,
+ new PersistableBundleWrapper(carrierConfig));
}
handleSubscriptionsChanged();
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
new file mode 100644
index 0000000..49b125c
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -0,0 +1,290 @@
+/*
+ * 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.server.wallpaper;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
+import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageDecoder;
+import android.graphics.Rect;
+import android.os.FileUtils;
+import android.os.SELinux;
+import android.util.Slog;
+import android.view.DisplayInfo;
+
+import com.android.server.utils.TimingsTraceAndSlog;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+
+/**
+ * Helper file for wallpaper cropping
+ * Meant to have a single instance, only used by the WallpaperManagerService
+ */
+class WallpaperCropper {
+
+ private static final String TAG = WallpaperCropper.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_CROP = true;
+
+ private final WallpaperDisplayHelper mWallpaperDisplayHelper;
+
+ WallpaperCropper(WallpaperDisplayHelper wallpaperDisplayHelper) {
+ mWallpaperDisplayHelper = wallpaperDisplayHelper;
+ }
+
+ /**
+ * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
+ * for display.
+ *
+ * This will generate the crop and write it in the file
+ */
+ void generateCrop(WallpaperData wallpaper) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.generateCrop");
+ generateCropInternal(wallpaper);
+ t.traceEnd();
+ }
+
+ private void generateCropInternal(WallpaperData wallpaper) {
+ boolean success = false;
+
+ // Only generate crop for default display.
+ final WallpaperDisplayHelper.DisplayData wpData =
+ mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
+ final Rect cropHint = new Rect(wallpaper.cropHint);
+ final DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(DEFAULT_DISPLAY);
+
+ if (DEBUG) {
+ Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
+ + Integer.toHexString(wallpaper.mWhich)
+ + " to " + wallpaper.cropFile.getName()
+ + " crop=(" + cropHint.width() + 'x' + cropHint.height()
+ + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
+ }
+
+ // Analyse the source; needed in multiple cases
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(wallpaper.wallpaperFile.getAbsolutePath(), options);
+ if (options.outWidth <= 0 || options.outHeight <= 0) {
+ Slog.w(TAG, "Invalid wallpaper data");
+ success = false;
+ } else {
+ boolean needCrop = false;
+ boolean needScale;
+
+ // Empty crop means use the full image
+ if (cropHint.isEmpty()) {
+ cropHint.left = cropHint.top = 0;
+ cropHint.right = options.outWidth;
+ cropHint.bottom = options.outHeight;
+ } else {
+ // force the crop rect to lie within the measured bounds
+ int dx = cropHint.right > options.outWidth ? options.outWidth - cropHint.right : 0;
+ int dy = cropHint.bottom > options.outHeight
+ ? options.outHeight - cropHint.bottom : 0;
+ cropHint.offset(dx, dy);
+
+ // If the crop hint was larger than the image we just overshot. Patch things up.
+ if (cropHint.left < 0) {
+ cropHint.left = 0;
+ }
+ if (cropHint.top < 0) {
+ cropHint.top = 0;
+ }
+
+ // Don't bother cropping if what we're left with is identity
+ needCrop = (options.outHeight > cropHint.height()
+ || options.outWidth > cropHint.width());
+ }
+
+ // scale if the crop height winds up not matching the recommended metrics
+ needScale = cropHint.height() > wpData.mHeight
+ || cropHint.height() > GLHelper.getMaxTextureSize()
+ || cropHint.width() > GLHelper.getMaxTextureSize();
+
+ //make sure screen aspect ratio is preserved if width is scaled under screen size
+ if (needScale) {
+ final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
+ final int newWidth = (int) (cropHint.width() * scaleByHeight);
+ if (newWidth < displayInfo.logicalWidth) {
+ final float screenAspectRatio =
+ (float) displayInfo.logicalHeight / (float) displayInfo.logicalWidth;
+ cropHint.bottom = (int) (cropHint.width() * screenAspectRatio);
+ needCrop = true;
+ }
+ }
+
+ if (DEBUG_CROP) {
+ Slog.v(TAG, "crop: w=" + cropHint.width() + " h=" + cropHint.height());
+ Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight);
+ Slog.v(TAG, "meas: w=" + options.outWidth + " h=" + options.outHeight);
+ Slog.v(TAG, "crop?=" + needCrop + " scale?=" + needScale);
+ }
+
+ if (!needCrop && !needScale) {
+ // Simple case: the nominal crop fits what we want, so we take
+ // the whole thing and just copy the image file directly.
+
+ // TODO: It is not accurate to estimate bitmap size without decoding it,
+ // may be we can try to remove this optimized way in the future,
+ // that means, we will always go into the 'else' block.
+
+ success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
+
+ if (!success) {
+ wallpaper.cropFile.delete();
+ // TODO: fall back to default wallpaper in this case
+ }
+
+ if (DEBUG) {
+ long estimateSize = (long) options.outWidth * options.outHeight * 4;
+ Slog.v(TAG, "Null crop of new wallpaper, estimate size="
+ + estimateSize + ", success=" + success);
+ }
+ } else {
+ // Fancy case: crop and scale. First, we decode and scale down if appropriate.
+ FileOutputStream f = null;
+ BufferedOutputStream bos = null;
+ try {
+ // This actually downsamples only by powers of two, but that's okay; we do
+ // a proper scaling blit later. This is to minimize transient RAM use.
+ // We calculate the largest power-of-two under the actual ratio rather than
+ // just let the decode take care of it because we also want to remap where the
+ // cropHint rectangle lies in the decoded [super]rect.
+ final int actualScale = cropHint.height() / wpData.mHeight;
+ int scale = 1;
+ while (2 * scale <= actualScale) {
+ scale *= 2;
+ }
+ options.inSampleSize = scale;
+ options.inJustDecodeBounds = false;
+
+ final Rect estimateCrop = new Rect(cropHint);
+ estimateCrop.scale(1f / options.inSampleSize);
+ final float hRatio = (float) wpData.mHeight / estimateCrop.height();
+ final int destHeight = (int) (estimateCrop.height() * hRatio);
+ final int destWidth = (int) (estimateCrop.width() * hRatio);
+
+ // We estimated an invalid crop, try to adjust the cropHint to get a valid one.
+ if (destWidth > GLHelper.getMaxTextureSize()) {
+ int newHeight = (int) (wpData.mHeight / hRatio);
+ int newWidth = (int) (wpData.mWidth / hRatio);
+
+ if (DEBUG) {
+ Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");
+ }
+
+ estimateCrop.set(cropHint);
+ estimateCrop.left += (cropHint.width() - newWidth) / 2;
+ estimateCrop.top += (cropHint.height() - newHeight) / 2;
+ estimateCrop.right = estimateCrop.left + newWidth;
+ estimateCrop.bottom = estimateCrop.top + newHeight;
+ cropHint.set(estimateCrop);
+ estimateCrop.scale(1f / options.inSampleSize);
+ }
+
+ // We've got the safe cropHint; now we want to scale it properly to
+ // the desired rectangle.
+ // That's a height-biased operation: make it fit the hinted height.
+ final int safeHeight = (int) (estimateCrop.height() * hRatio);
+ final int safeWidth = (int) (estimateCrop.width() * hRatio);
+
+ if (DEBUG_CROP) {
+ Slog.v(TAG, "Decode parameters:");
+ Slog.v(TAG, " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
+ Slog.v(TAG, " down sampling=" + options.inSampleSize
+ + ", hRatio=" + hRatio);
+ Slog.v(TAG, " dest=" + destWidth + "x" + destHeight);
+ Slog.v(TAG, " safe=" + safeWidth + "x" + safeHeight);
+ Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize());
+ }
+
+ //Create a record file and will delete if ImageDecoder work well.
+ final String recordName =
+ (wallpaper.wallpaperFile.getName().equals(WALLPAPER)
+ ? RECORD_FILE : RECORD_LOCK_FILE);
+ final File record = new File(getWallpaperDir(wallpaper.userId), recordName);
+ record.createNewFile();
+ Slog.v(TAG, "record path =" + record.getPath()
+ + ", record name =" + record.getName());
+
+ final ImageDecoder.Source srcData =
+ ImageDecoder.createSource(wallpaper.wallpaperFile);
+ final int sampleSize = scale;
+ Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
+ decoder.setTargetSampleSize(sampleSize);
+ decoder.setCrop(estimateCrop);
+ });
+
+ record.delete();
+
+ if (cropped == null) {
+ Slog.e(TAG, "Could not decode new wallpaper");
+ } else {
+ // We are safe to create final crop with safe dimensions now.
+ final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
+ safeWidth, safeHeight, true);
+ if (DEBUG) {
+ Slog.v(TAG, "Final extract:");
+ Slog.v(TAG, " dims: w=" + wpData.mWidth
+ + " h=" + wpData.mHeight);
+ Slog.v(TAG, " out: w=" + finalCrop.getWidth()
+ + " h=" + finalCrop.getHeight());
+ }
+
+ f = new FileOutputStream(wallpaper.cropFile);
+ bos = new BufferedOutputStream(f, 32 * 1024);
+ finalCrop.compress(Bitmap.CompressFormat.PNG, 100, bos);
+ // don't rely on the implicit flush-at-close when noting success
+ bos.flush();
+ success = true;
+ }
+ } catch (Exception e) {
+ if (DEBUG) {
+ Slog.e(TAG, "Error decoding crop", e);
+ }
+ } finally {
+ IoUtils.closeQuietly(bos);
+ IoUtils.closeQuietly(f);
+ }
+ }
+ }
+
+ if (!success) {
+ Slog.e(TAG, "Unable to apply new wallpaper");
+ wallpaper.cropFile.delete();
+ }
+
+ if (wallpaper.cropFile.exists()) {
+ boolean didRestorecon = SELinux.restorecon(wallpaper.cropFile.getAbsoluteFile());
+ if (DEBUG) {
+ Slog.v(TAG, "restorecon() of crop file returned " + didRestorecon);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index a380dea..f02ee66 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -27,7 +27,6 @@
import android.view.Display;
import android.view.DisplayInfo;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.WindowManagerInternal;
import java.util.function.Consumer;
@@ -36,7 +35,6 @@
*/
class WallpaperDisplayHelper {
- @VisibleForTesting
static final class DisplayData {
int mWidth = -1;
int mHeight = -1;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b146767..bf09b67 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -74,7 +74,6 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
-import android.graphics.ImageDecoder;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
@@ -109,7 +108,6 @@
import android.util.SparseBooleanArray;
import android.util.Xml;
import android.view.Display;
-import android.view.DisplayInfo;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -132,7 +130,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -308,7 +305,7 @@
}
loadSettingsLocked(wallpaper.userId, true);
}
- generateCrop(wallpaper);
+ mWallpaperCropper.generateCrop(wallpaper);
if (DEBUG) {
Slog.v(TAG, "Crop done; invoking completion callback");
}
@@ -593,233 +590,6 @@
return colors;
}
- /**
- * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
- * for display.
- */
- void generateCrop(WallpaperData wallpaper) {
- TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
- t.traceBegin("WPMS.generateCrop");
- generateCropInternal(wallpaper);
- t.traceEnd();
- }
-
- private void generateCropInternal(WallpaperData wallpaper) {
- boolean success = false;
-
- // Only generate crop for default display.
- final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
- final Rect cropHint = new Rect(wallpaper.cropHint);
- final DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(DEFAULT_DISPLAY);
-
- if (DEBUG) {
- Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
- + Integer.toHexString(wallpaper.mWhich)
- + " to " + wallpaper.cropFile.getName()
- + " crop=(" + cropHint.width() + 'x' + cropHint.height()
- + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
- }
-
- // Analyse the source; needed in multiple cases
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(wallpaper.wallpaperFile.getAbsolutePath(), options);
- if (options.outWidth <= 0 || options.outHeight <= 0) {
- Slog.w(TAG, "Invalid wallpaper data");
- success = false;
- } else {
- boolean needCrop = false;
- boolean needScale = false;
-
- // Empty crop means use the full image
- if (cropHint.isEmpty()) {
- cropHint.left = cropHint.top = 0;
- cropHint.right = options.outWidth;
- cropHint.bottom = options.outHeight;
- } else {
- // force the crop rect to lie within the measured bounds
- cropHint.offset(
- (cropHint.right > options.outWidth ? options.outWidth - cropHint.right : 0),
- (cropHint.bottom > options.outHeight ? options.outHeight - cropHint.bottom : 0));
-
- // If the crop hint was larger than the image we just overshot. Patch things up.
- if (cropHint.left < 0) {
- cropHint.left = 0;
- }
- if (cropHint.top < 0) {
- cropHint.top = 0;
- }
-
- // Don't bother cropping if what we're left with is identity
- needCrop = (options.outHeight > cropHint.height()
- || options.outWidth > cropHint.width());
- }
-
- // scale if the crop height winds up not matching the recommended metrics
- needScale = cropHint.height() > wpData.mHeight
- || cropHint.height() > GLHelper.getMaxTextureSize()
- || cropHint.width() > GLHelper.getMaxTextureSize();
-
- //make sure screen aspect ratio is preserved if width is scaled under screen size
- if (needScale) {
- final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
- final int newWidth = (int) (cropHint.width() * scaleByHeight);
- if (newWidth < displayInfo.logicalWidth) {
- final float screenAspectRatio =
- (float) displayInfo.logicalHeight / (float) displayInfo.logicalWidth;
- cropHint.bottom = (int) (cropHint.width() * screenAspectRatio);
- needCrop = true;
- }
- }
-
- if (DEBUG_CROP) {
- Slog.v(TAG, "crop: w=" + cropHint.width() + " h=" + cropHint.height());
- Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight);
- Slog.v(TAG, "meas: w=" + options.outWidth + " h=" + options.outHeight);
- Slog.v(TAG, "crop?=" + needCrop + " scale?=" + needScale);
- }
-
- if (!needCrop && !needScale) {
- // Simple case: the nominal crop fits what we want, so we take
- // the whole thing and just copy the image file directly.
-
- // TODO: It is not accurate to estimate bitmap size without decoding it,
- // may be we can try to remove this optimized way in the future,
- // that means, we will always go into the 'else' block.
-
- success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
-
- if (!success) {
- wallpaper.cropFile.delete();
- // TODO: fall back to default wallpaper in this case
- }
-
- if (DEBUG) {
- long estimateSize = (long) options.outWidth * options.outHeight * 4;
- Slog.v(TAG, "Null crop of new wallpaper, estimate size="
- + estimateSize + ", success=" + success);
- }
- } else {
- // Fancy case: crop and scale. First, we decode and scale down if appropriate.
- FileOutputStream f = null;
- BufferedOutputStream bos = null;
- try {
- // This actually downsamples only by powers of two, but that's okay; we do
- // a proper scaling blit later. This is to minimize transient RAM use.
- // We calculate the largest power-of-two under the actual ratio rather than
- // just let the decode take care of it because we also want to remap where the
- // cropHint rectangle lies in the decoded [super]rect.
- final int actualScale = cropHint.height() / wpData.mHeight;
- int scale = 1;
- while (2 * scale <= actualScale) {
- scale *= 2;
- }
- options.inSampleSize = scale;
- options.inJustDecodeBounds = false;
-
- final Rect estimateCrop = new Rect(cropHint);
- estimateCrop.scale(1f / options.inSampleSize);
- final float hRatio = (float) wpData.mHeight / estimateCrop.height();
- final int destHeight = (int) (estimateCrop.height() * hRatio);
- final int destWidth = (int) (estimateCrop.width() * hRatio);
-
- // We estimated an invalid crop, try to adjust the cropHint to get a valid one.
- if (destWidth > GLHelper.getMaxTextureSize()) {
- int newHeight = (int) (wpData.mHeight / hRatio);
- int newWidth = (int) (wpData.mWidth / hRatio);
-
- if (DEBUG) {
- Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");
- }
-
- estimateCrop.set(cropHint);
- estimateCrop.left += (cropHint.width() - newWidth) / 2;
- estimateCrop.top += (cropHint.height() - newHeight) / 2;
- estimateCrop.right = estimateCrop.left + newWidth;
- estimateCrop.bottom = estimateCrop.top + newHeight;
- cropHint.set(estimateCrop);
- estimateCrop.scale(1f / options.inSampleSize);
- }
-
- // We've got the safe cropHint; now we want to scale it properly to
- // the desired rectangle.
- // That's a height-biased operation: make it fit the hinted height.
- final int safeHeight = (int) (estimateCrop.height() * hRatio);
- final int safeWidth = (int) (estimateCrop.width() * hRatio);
-
- if (DEBUG_CROP) {
- Slog.v(TAG, "Decode parameters:");
- Slog.v(TAG, " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
- Slog.v(TAG, " down sampling=" + options.inSampleSize
- + ", hRatio=" + hRatio);
- Slog.v(TAG, " dest=" + destWidth + "x" + destHeight);
- Slog.v(TAG, " safe=" + safeWidth + "x" + safeHeight);
- Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize());
- }
-
- //Create a record file and will delete if ImageDecoder work well.
- final String recordName =
- (wallpaper.wallpaperFile.getName().equals(WALLPAPER)
- ? RECORD_FILE : RECORD_LOCK_FILE);
- final File record = new File(getWallpaperDir(wallpaper.userId), recordName);
- record.createNewFile();
- Slog.v(TAG, "record path =" + record.getPath()
- + ", record name =" + record.getName());
-
- final ImageDecoder.Source srcData =
- ImageDecoder.createSource(wallpaper.wallpaperFile);
- final int sampleSize = scale;
- Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
- decoder.setTargetSampleSize(sampleSize);
- decoder.setCrop(estimateCrop);
- });
-
- record.delete();
-
- if (cropped == null) {
- Slog.e(TAG, "Could not decode new wallpaper");
- } else {
- // We are safe to create final crop with safe dimensions now.
- final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
- safeWidth, safeHeight, true);
- if (DEBUG) {
- Slog.v(TAG, "Final extract:");
- Slog.v(TAG, " dims: w=" + wpData.mWidth
- + " h=" + wpData.mHeight);
- Slog.v(TAG, " out: w=" + finalCrop.getWidth()
- + " h=" + finalCrop.getHeight());
- }
-
- f = new FileOutputStream(wallpaper.cropFile);
- bos = new BufferedOutputStream(f, 32*1024);
- finalCrop.compress(Bitmap.CompressFormat.PNG, 100, bos);
- bos.flush(); // don't rely on the implicit flush-at-close when noting success
- success = true;
- }
- } catch (Exception e) {
- if (DEBUG) {
- Slog.e(TAG, "Error decoding crop", e);
- }
- } finally {
- IoUtils.closeQuietly(bos);
- IoUtils.closeQuietly(f);
- }
- }
- }
-
- if (!success) {
- Slog.e(TAG, "Unable to apply new wallpaper");
- wallpaper.cropFile.delete();
- }
-
- if (wallpaper.cropFile.exists()) {
- boolean didRestorecon = SELinux.restorecon(wallpaper.cropFile.getAbsoluteFile());
- if (DEBUG) {
- Slog.v(TAG, "restorecon() of crop file returned " + didRestorecon);
- }
- }
- }
-
private final Context mContext;
private final WindowManagerInternal mWindowManagerInternal;
private final IPackageManager mIPackageManager;
@@ -912,6 +682,7 @@
@VisibleForTesting
final WallpaperDisplayHelper mWallpaperDisplayHelper;
+ final WallpaperCropper mWallpaperCropper;
private boolean supportsMultiDisplay(WallpaperConnection connection) {
if (connection != null) {
@@ -1567,6 +1338,7 @@
DisplayManager dm = mContext.getSystemService(DisplayManager.class);
dm.registerDisplayListener(mDisplayListener, null /* handler */);
mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
+ mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
mActivityManager = mContext.getSystemService(ActivityManager.class);
mMonitor = new MyPackageMonitor();
mColorsChangedListeners = new SparseArray<>();
@@ -1617,7 +1389,7 @@
if (DEBUG) {
Slog.i(TAG, "No crop; regenerating from source");
}
- generateCrop(wallpaper);
+ mWallpaperCropper.generateCrop(wallpaper);
}
// Still nothing? Fall back to default.
if (!wallpaper.cropExists()) {
@@ -3479,7 +3251,7 @@
mWallpaperMap.put(userId, wallpaper);
if (!wallpaper.cropExists()) {
if (wallpaper.sourceExists()) {
- generateCrop(wallpaper);
+ mWallpaperCropper.generateCrop(wallpaper);
} else {
Slog.i(TAG, "No static wallpaper imagery; defaults will be shown");
}
@@ -3700,7 +3472,7 @@
if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success
+ " id=" + wallpaper.wallpaperId);
if (success) {
- generateCrop(wallpaper); // based on the new image + metadata
+ mWallpaperCropper.generateCrop(wallpaper); // based on the new image + metadata
bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, true, false,
wallpaper, null);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 90d25ee..90ac1aa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5257,9 +5257,9 @@
transferStartingWindowFromHiddenAboveTokenIfNeeded();
}
- // If in a transition, defer commits for activities that are going invisible
- if (!visible && inTransition()) {
- if (mTransitionController.inPlayingTransition(this)
+ // Defer committing visibility until transition starts.
+ if (inTransition()) {
+ if (!visible && mTransitionController.inPlayingTransition(this)
&& mTransitionController.isCollecting(this)) {
mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
}
@@ -5509,6 +5509,17 @@
}
}
+ /** Updates draw state and shows drawn windows. */
+ void commitFinishDrawing(SurfaceControl.Transaction t) {
+ boolean committed = false;
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ committed |= mChildren.get(i).commitFinishDrawing(t);
+ }
+ if (committed) {
+ requestUpdateWallpaperIfNeeded();
+ }
+ }
+
/**
* Check if visibility of this {@link ActivityRecord} should be updated as part of an app
* transition.
@@ -6567,12 +6578,29 @@
updateReportedVisibilityLocked();
}
+ /**
+ * Sets whether something has been visible in the task and returns {@code true} if the state
+ * is changed from invisible to visible.
+ */
+ private boolean setTaskHasBeenVisible() {
+ final boolean wasTaskVisible = task.getHasBeenVisible();
+ if (wasTaskVisible) {
+ return false;
+ }
+ if (inTransition()) {
+ // The deferring will be canceled until transition is ready so it won't dispatch
+ // intermediate states to organizer.
+ task.setDeferTaskAppear(true);
+ }
+ task.setHasBeenVisible(true);
+ return true;
+ }
+
void onStartingWindowDrawn() {
boolean wasTaskVisible = false;
if (task != null) {
mSplashScreenStyleSolidColor = true;
- wasTaskVisible = task.getHasBeenVisible();
- task.setHasBeenVisible(true);
+ wasTaskVisible = !setTaskHasBeenVisible();
}
// The transition may not be executed if the starting process hasn't attached. But if the
@@ -6610,7 +6638,7 @@
}
finishLaunchTickingLocked();
if (task != null) {
- task.setHasBeenVisible(true);
+ setTaskHasBeenVisible();
}
// Clear indicated launch root task because there's no trampoline activity to expect after
// the windows are drawn.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index bd4f1a6..2bd9052 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -24,6 +24,7 @@
import android.app.BackgroundStartPrivileges;
import android.app.IActivityManager;
import android.app.IApplicationThread;
+import android.app.ITaskStackListener;
import android.app.ProfilerInfo;
import android.content.ComponentName;
import android.content.IIntentSender;
@@ -740,4 +741,10 @@
*/
public abstract void restartTaskActivityProcessIfVisible(
int taskId, @NonNull String packageName);
+
+ /** Sets the task stack listener that gets callbacks when a task stack changes. */
+ public abstract void registerTaskStackListener(ITaskStackListener listener);
+
+ /** Unregister a task stack listener so that it stops receiving callbacks. */;
+ public abstract void unregisterTaskStackListener(ITaskStackListener listener);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6fe77b7..a927ed3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6916,5 +6916,17 @@
activity.restartProcessIfVisible();
}
}
+
+ /** Sets the task stack listener that gets callbacks when a task stack changes. */
+ @Override
+ public void registerTaskStackListener(ITaskStackListener listener) {
+ ActivityTaskManagerService.this.registerTaskStackListener(listener);
+ }
+
+ /** Unregister a task stack listener so that it stops receiving callbacks. */
+ @Override
+ public void unregisterTaskStackListener(ITaskStackListener listener) {
+ ActivityTaskManagerService.this.unregisterTaskStackListener(listener);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a68d7af..cfcf459 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -129,6 +129,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.internal.util.function.TriConsumer;
import com.android.internal.view.AppearanceRegion;
import com.android.internal.widget.PointerLocationView;
@@ -2451,8 +2452,9 @@
*/
public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
- mScreenshotHelper.takeScreenshot(screenshotType,
- source, mHandler, null /* completionConsumer */);
+ ScreenshotRequest request =
+ new ScreenshotRequest.Builder(screenshotType, source).build();
+ mScreenshotHelper.takeScreenshot(request, mHandler, null /* completionConsumer */);
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index f916ee4..800fe09 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -197,6 +197,12 @@
// Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
+ // Whether using display aspect ratio as a default aspect ratio for all letterboxed apps.
+ // mIsSplitScreenAspectRatioForUnresizableAppsEnabled and
+ // config_letterboxDefaultMinAspectRatioForUnresizableApps take priority over this for
+ // unresizable apps
+ private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
+
// Whether letterboxing strategy is enabled for translucent activities. If {@value false}
// all the feature is disabled
private boolean mTranslucentLetterboxingEnabled;
@@ -288,6 +294,9 @@
R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+ mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox = mContext.getResources()
+ .getBoolean(R.bool
+ .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled);
mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsEnabledForTranslucentActivities);
mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
@@ -943,6 +952,13 @@
}
/**
+ * Whether using display aspect ratio as a default aspect ratio for all letterboxed apps.
+ */
+ boolean getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox() {
+ return mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
+ }
+
+ /**
* Overrides whether using split screen aspect ratio as a default aspect ratio for unresizable
* apps.
*/
@@ -951,6 +967,14 @@
}
/**
+ * Overrides whether using display aspect ratio as a default aspect ratio for all letterboxed
+ * apps.
+ */
+ void setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(boolean enabled) {
+ mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox = enabled;
+ }
+
+ /**
* Resets whether using split screen aspect ratio as a default aspect ratio for unresizable
* apps {@link R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled}.
*/
@@ -959,6 +983,16 @@
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
}
+ /**
+ * Resets whether using display aspect ratio as a default aspect ratio for all letterboxed
+ * apps {@link R.bool.config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled}.
+ */
+ void resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox() {
+ mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox = mContext.getResources()
+ .getBoolean(R.bool
+ .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled);
+ }
+
boolean isTranslucentLetterboxingEnabled() {
return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
&& isTranslucentLetterboxingAllowed());
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9c43c1d..67e188f 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -584,7 +584,7 @@
? getSplitScreenAspectRatio()
: mActivityRecord.shouldCreateCompatDisplayInsets()
? getDefaultMinAspectRatioForUnresizableApps()
- : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ : getDefaultMinAspectRatio();
}
private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -593,7 +593,7 @@
return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
> MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
- : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ : getDefaultMinAspectRatio();
}
return getSplitScreenAspectRatio();
@@ -621,6 +621,16 @@
return computeAspectRatio(bounds);
}
+ private float getDefaultMinAspectRatio() {
+ final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+ if (displayContent == null
+ || !mLetterboxConfiguration
+ .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
+ return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ }
+ return computeAspectRatio(new Rect(displayContent.getBounds()));
+ }
+
Resources getResources() {
return mActivityRecord.mWmService.mContext.getResources();
}
@@ -1014,6 +1024,9 @@
+ mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps());
pw.println(prefix + " isSplitScreenAspectRatioForUnresizableAppsEnabled="
+ mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
+ pw.println(prefix + " isDisplayAspectRatioEnabledForFixedOrientationLetterbox="
+ + mLetterboxConfiguration
+ .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
}
/**
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 15a5ebf..97e0b1e 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -310,6 +310,11 @@
}
}
+ @Override
+ public void performHapticFeedbackAsync(int effectId, boolean always) {
+ performHapticFeedback(effectId, always);
+ }
+
/* Drag/drop */
@Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b7021c8..1b59d8d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3271,12 +3271,17 @@
// We intend to let organizer manage task visibility but it doesn't
// have enough information until we finish shell transitions.
// In the mean time we do an easy fix here.
- final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS | CHILDREN);
+ final boolean visible = isVisible();
+ final boolean show = visible || isAnimating(TRANSITION | PARENTS | CHILDREN);
if (mSurfaceControl != null) {
if (show != mLastSurfaceShowing) {
t.setVisibility(mSurfaceControl, show);
}
}
+ // Only show the overlay if the task has other visible children
+ if (mOverlayHost != null) {
+ mOverlayHost.setVisibility(t, visible);
+ }
mLastSurfaceShowing = show;
}
@@ -4148,21 +4153,28 @@
void setHasBeenVisible(boolean hasBeenVisible) {
mHasBeenVisible = hasBeenVisible;
- if (hasBeenVisible) {
- if (!mDeferTaskAppear) sendTaskAppeared();
- if (!isRootTask()) {
- getRootTask().setHasBeenVisible(true);
+ if (!hasBeenVisible || mDeferTaskAppear) {
+ return;
+ }
+ sendTaskAppeared();
+ for (WindowContainer<?> parent = getParent(); parent != null; parent = parent.getParent()) {
+ final Task parentTask = parent.asTask();
+ if (parentTask == null) {
+ break;
}
+ parentTask.setHasBeenVisible(true);
}
}
+
boolean getHasBeenVisible() {
return mHasBeenVisible;
}
void setDeferTaskAppear(boolean deferTaskAppear) {
+ final boolean wasDeferred = mDeferTaskAppear;
mDeferTaskAppear = deferTaskAppear;
- if (!mDeferTaskAppear) {
+ if (wasDeferred && !deferTaskAppear) {
sendTaskAppeared();
}
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index cd23959..7d3367f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -70,6 +70,7 @@
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -211,6 +212,8 @@
private IContainerFreezer mContainerFreezer = null;
private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+ final TransitionController.Logger mLogger = new TransitionController.Logger();
+
Transition(@TransitionType int type, @TransitionFlags int flags,
TransitionController controller, BLASTSyncEngine syncEngine) {
mType = type;
@@ -219,6 +222,8 @@
mSyncEngine = syncEngine;
mToken = new Token(this);
+ mLogger.mCreateWallTimeMs = System.currentTimeMillis();
+ mLogger.mCreateTimeNs = SystemClock.uptimeNanos();
controller.mTransitionTracer.logState(this);
}
@@ -380,6 +385,8 @@
mState = STATE_COLLECTING;
mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method);
+ mLogger.mSyncId = mSyncId;
+ mLogger.mCollectTimeNs = SystemClock.uptimeNanos();
mController.mTransitionTracer.logState(this);
}
@@ -399,6 +406,7 @@
mSyncId);
applyReady();
+ mLogger.mStartTimeNs = SystemClock.uptimeNanos();
mController.mTransitionTracer.logState(this);
mController.updateAnimatingState(mTmpTransaction);
@@ -608,6 +616,7 @@
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Set transition ready=%b %d", ready, mSyncId);
mSyncEngine.setReady(mSyncId, ready);
+ if (ready) mLogger.mReadyTimeNs = SystemClock.uptimeNanos();
}
/**
@@ -759,6 +768,8 @@
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
}
+ mLogger.mFinishTimeNs = SystemClock.uptimeNanos();
+ mController.mLoggerHandler.post(mLogger::logOnFinish);
// Close the transactions now. They were originally copied to Shell in case we needed to
// apply them due to a remote failure. Since we don't need to apply them anymore, free them
// immediately.
@@ -845,7 +856,7 @@
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
if (inputSinkTransaction == null) {
- inputSinkTransaction = new SurfaceControl.Transaction();
+ inputSinkTransaction = ar.mWmService.mTransactionFactory.get();
}
ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction);
}
@@ -961,6 +972,12 @@
// time being, we don't have full cross-display transitions so it isn't a problem.
final DisplayContent dc = mTargetDisplays.get(0);
+ // Commit the visibility of visible activities before calculateTransitionInfo(), so the
+ // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
+ // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
+ // needs to be updated for STATE_ABORT.
+ commitVisibleActivities(transaction);
+
if (mState == STATE_ABORT) {
mController.abort(this);
dc.getPendingTransaction().merge(transaction);
@@ -1082,6 +1099,8 @@
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
+ mLogger.mSendTimeNs = SystemClock.uptimeNanos();
+ mLogger.mInfo = info;
mController.getTransitionPlayer().onTransitionReady(
mToken, info, transaction, mFinishTransaction);
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
@@ -1097,6 +1116,7 @@
// No player registered, so just finish/apply immediately
cleanUpOnFailure();
}
+ mController.mLoggerHandler.post(mLogger::logOnSend);
mOverrideOptions = null;
reportStartReasonsToLogger();
@@ -1132,6 +1152,22 @@
}
}
+ /** The transition is ready to play. Make the start transaction show the surfaces. */
+ private void commitVisibleActivities(SurfaceControl.Transaction transaction) {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+ if (ar == null || ar.getTask() == null) {
+ continue;
+ }
+ if (ar.isVisibleRequested()) {
+ ar.commitVisibility(true /* visible */, false /* performLayout */,
+ true /* fromTransition */);
+ ar.commitFinishDrawing(transaction);
+ }
+ ar.getTask().setDeferTaskAppear(false);
+ }
+ }
+
/** @see RecentsAnimationController#attachNavigationBarToApp */
private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 73cd251..5e116ba 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.WindowConfiguration;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
@@ -38,6 +39,7 @@
import android.os.Trace;
import android.util.ArrayMap;
import android.util.Slog;
+import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -46,11 +48,13 @@
import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -120,6 +124,8 @@
private boolean mAnimatingState = false;
+ final Handler mLoggerHandler = FgThread.getHandler();
+
TransitionController(ActivityTaskManagerService atm,
TaskSnapshotController taskSnapshotController,
TransitionTracer transitionTracer) {
@@ -462,9 +468,11 @@
info = new ActivityManager.RunningTaskInfo();
startTask.fillTaskInfo(info);
}
- mTransitionPlayer.requestStartTransition(transition.getToken(),
- new TransitionRequestInfo(transition.mType, info, remoteTransition,
- displayChange));
+ final TransitionRequestInfo request = new TransitionRequestInfo(
+ transition.mType, info, remoteTransition, displayChange);
+ transition.mLogger.mRequestTimeNs = SystemClock.uptimeNanos();
+ transition.mLogger.mRequest = request;
+ mTransitionPlayer.requestStartTransition(transition.getToken(), request);
transition.setRemoteTransition(remoteTransition);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting transition", e);
@@ -847,6 +855,66 @@
}
}
+ /**
+ * Data-class to store recorded events/info for a transition. This allows us to defer the
+ * actual logging until the system isn't busy. This also records some common metrics to see
+ * delays at-a-glance.
+ *
+ * Beside `mCreateWallTimeMs`, all times are elapsed times and will all be reported relative
+ * to when the transition was created.
+ */
+ static class Logger {
+ long mCreateWallTimeMs;
+ long mCreateTimeNs;
+ long mRequestTimeNs;
+ long mCollectTimeNs;
+ long mStartTimeNs;
+ long mReadyTimeNs;
+ long mSendTimeNs;
+ long mFinishTimeNs;
+ TransitionRequestInfo mRequest;
+ WindowContainerTransaction mStartWCT;
+ int mSyncId;
+ TransitionInfo mInfo;
+
+ private String buildOnSendLog() {
+ StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId)
+ .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+ if (mRequest != null) {
+ sb.append(" via request=").append(mRequest);
+ }
+ return sb.toString();
+ }
+
+ void logOnSend() {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " startWCT=%s", mStartWCT);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s", mInfo);
+ }
+
+ private static String toMsString(long nanos) {
+ return ((double) Math.round((double) nanos / 1000) / 1000) + "ms";
+ }
+
+ private String buildOnFinishLog() {
+ StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId)
+ .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+ sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs));
+ if (mRequestTimeNs != 0) {
+ sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs));
+ }
+ sb.append(" started=").append(toMsString(mStartTimeNs - mCreateTimeNs));
+ sb.append(" ready=").append(toMsString(mReadyTimeNs - mCreateTimeNs));
+ sb.append(" sent=").append(toMsString(mSendTimeNs - mCreateTimeNs));
+ sb.append(" finished=").append(toMsString(mFinishTimeNs - mCreateTimeNs));
+ return sb.toString();
+ }
+
+ void logOnFinish() {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnFinishLog());
+ }
+ }
+
static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index 975b21c..88c410b 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -80,6 +80,12 @@
}
}
+ void setVisibility(SurfaceControl.Transaction t, boolean visible) {
+ if (mSurfaceControl != null) {
+ t.setVisibility(mSurfaceControl, visible);
+ }
+ }
+
void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
requireOverlaySurfaceControl();
mOverlays.add(p);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8767096..dc32769 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -427,7 +427,7 @@
* @see #ENABLE_SHELL_TRANSITIONS
*/
public static final boolean sEnableShellTransitions =
- SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false);
+ SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, true);
/**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
@@ -3174,7 +3174,8 @@
/**
* Moves the given display to the top. If it cannot be moved to the top this method does
- * nothing.
+ * nothing (e.g. if the display has the flag FLAG_STEAL_TOP_FOCUS_DISABLED set).
+ * @param displayId The display to move to the top.
*/
void moveDisplayToTopInternal(int displayId) {
synchronized (mGlobalLock) {
@@ -3189,14 +3190,6 @@
return;
}
- if (mPerDisplayFocusEnabled) {
- ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
- "Not moving display (displayId=%d) to top. Top focused displayId=%d. "
- + "Reason: config_perDisplayFocusEnabled", displayId,
- mRoot.getTopFocusedDisplayContent().getDisplayId());
- return;
- }
-
// Nothing prevented us from moving the display to the top. Let's do it!
displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP,
displayContent, true /* includingParents */);
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index e2c9c17..e931175 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -970,6 +970,10 @@
runSetBooleanFlag(pw, mLetterboxConfiguration
::setIsSplitScreenAspectRatioForUnresizableAppsEnabled);
break;
+ case "--isDisplayAspectRatioEnabledForFixedOrientationLetterbox":
+ runSetBooleanFlag(pw, mLetterboxConfiguration
+ ::setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox);
+ break;
case "--isTranslucentLetterboxingEnabled":
runSetBooleanFlag(pw, mLetterboxConfiguration
::setTranslucentLetterboxingOverrideEnabled);
@@ -1045,6 +1049,10 @@
mLetterboxConfiguration
.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
break;
+ case "IsDisplayAspectRatioEnabledForFixedOrientationLetterbox":
+ mLetterboxConfiguration
+ .resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
+ break;
case "isTranslucentLetterboxingEnabled":
mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
break;
@@ -1155,6 +1163,7 @@
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
mLetterboxConfiguration.resetIsEducationEnabled();
mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+ mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
@@ -1202,7 +1211,9 @@
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
+ mLetterboxConfiguration
.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
-
+ pw.println("Is using display aspect ratio as aspect ratio for all letterboxed apps: "
+ + mLetterboxConfiguration
+ .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
pw.println(" Is activity \"refresh\" in camera compatibility treatment enabled: "
+ mLetterboxConfiguration.isCameraCompatRefreshEnabled());
pw.println(" Refresh using \"stopped -> resumed\" cycle: "
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6a1adb4..5c68b12 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -318,6 +318,7 @@
transition = mTransitionController.createTransition(type);
}
transition.start();
+ transition.mLogger.mStartWCT = wct;
applyTransaction(wct, -1 /*syncId*/, transition, caller);
if (needsSetReady) {
transition.setAllReady();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 828a89a..33af563 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4616,6 +4616,19 @@
}
}
+ /** Makes the surface of drawn window (COMMIT_DRAW_PENDING) to be visible. */
+ boolean commitFinishDrawing(SurfaceControl.Transaction t) {
+ boolean committed = mWinAnimator.commitFinishDrawingLocked();
+ if (committed) {
+ // Ensure that the visibility of buffer layer is set.
+ mWinAnimator.prepareSurfaceLocked(t);
+ }
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ committed |= mChildren.get(i).commitFinishDrawing(t);
+ }
+ return committed;
+ }
+
// This must be called while inside a transaction.
boolean performShowLocked() {
if (!showToCurrentUser()) {
@@ -6228,7 +6241,6 @@
@Override
public void handleTapOutsideFocusInsideSelf() {
- final DisplayContent displayContent = getDisplayContent();
mWmService.moveDisplayToTopInternal(getDisplayId());
mWmService.handleTaskFocusChange(getTask(), mActivityRecord);
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 40e74b1..7c73768 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1489,7 +1489,7 @@
mServiceObj, gServiceClassInfo.getContextForDisplay, displayId));
for (int32_t iconId = static_cast<int32_t>(PointerIconStyle::TYPE_CONTEXT_MENU);
- iconId <= static_cast<int32_t>(PointerIconStyle::TYPE_GRABBING); ++iconId) {
+ iconId <= static_cast<int32_t>(PointerIconStyle::TYPE_HANDWRITING); ++iconId) {
const PointerIconStyle pointerIconStyle = static_cast<PointerIconStyle>(iconId);
PointerIcon pointerIcon;
loadSystemIconAsSpriteWithPointerIcon(env, displayContext.get(), pointerIconStyle,
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index acfa491..351afb9 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -35,7 +35,7 @@
import java.util.ArrayList;
/**
- * Central session for a single {@link CredentialManager#executeCreateCredential} request.
+ * Central session for a single {@link CredentialManager#createCredential} request.
* This class listens to the responses from providers, and the UX app, and updates the
* provider(s) state maintained in {@link ProviderCreateSession}.
*/
@@ -107,9 +107,14 @@
}
@Override
- public void onUiCancellation() {
- respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_USER_CANCELED,
- "User cancelled the selector");
+ public void onUiCancellation(boolean isUserCancellation) {
+ if (isUserCancellation) {
+ respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_USER_CANCELED,
+ "User cancelled the selector");
+ } else {
+ respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_INTERRUPTED,
+ "The UI was interrupted - please try again.");
+ }
}
private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
new file mode 100644
index 0000000..b7c5fc2
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -0,0 +1,125 @@
+/*
+ * 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.server.credentials;
+
+import android.credentials.CredentialDescription;
+import android.credentials.IRegisterCredentialDescriptionCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Contains information on what CredentialProvider has what provisioned Credential. */
+public class CredentialDescriptionRegistry {
+
+ private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
+ private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser;
+
+ static {
+ sCredentialDescriptionSessionPerUser = new SparseArray<>();
+ }
+
+ // TODO(b/265992655): add a way to update CredentialRegistry when a user is removed.
+ /** Get and/or create a {@link CredentialDescription} for the given user id. */
+ public static CredentialDescriptionRegistry forUser(int userId) {
+ CredentialDescriptionRegistry session =
+ sCredentialDescriptionSessionPerUser.get(userId, null);
+
+ if (session == null) {
+ session = new CredentialDescriptionRegistry();
+ sCredentialDescriptionSessionPerUser.put(userId, session);
+ }
+ return session;
+ }
+
+ private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
+
+ private CredentialDescriptionRegistry() {
+ this.mCredentialDescriptions = new HashMap<>();
+ }
+
+ /** Handle the given {@link RegisterCredentialDescriptionRequest} by creating
+ * the appropriate package name mapping. */
+ public void executeRegisterRequest(RegisterCredentialDescriptionRequest request,
+ String callingPackageName,
+ IRegisterCredentialDescriptionCallback callback) {
+
+ if (!mCredentialDescriptions.containsKey(callingPackageName)
+ && mCredentialDescriptions.size() <= MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS) {
+ mCredentialDescriptions.put(callingPackageName, new HashSet<>());
+ }
+
+ mCredentialDescriptions.get(callingPackageName)
+ .addAll(request.getCredentialDescriptions());
+
+ try {
+ callback.onResponse();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Handle the given {@link UnregisterCredentialDescriptionRequest} by creating
+ * the appropriate package name mapping. */
+ public void executeUnregisterRequest(
+ UnregisterCredentialDescriptionRequest request,
+ String callingPackageName,
+ IUnregisterCredentialDescriptionCallback callback) {
+
+ if (mCredentialDescriptions.containsKey(callingPackageName)) {
+ mCredentialDescriptions.get(callingPackageName)
+ .remove(request.getCredentialDescription());
+ }
+
+ try {
+ callback.onResponse();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Returns package names of CredentialProviders that can satisfy a given
+ * {@link CredentialDescription}. */
+ public Set<String> filterCredentials(String flatRequestString) {
+
+ Set<String> result = new HashSet<>();
+
+ for (String componentName: mCredentialDescriptions.keySet()) {
+ Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName);
+ for (CredentialDescription containedDescription: currentSet) {
+ if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) {
+ result.add(componentName);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ void evictProviderWithPackageName(String packageName) {
+ if (mCredentialDescriptions.containsKey(packageName)) {
+ mCredentialDescriptions.remove(packageName);
+ }
+ }
+
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index f76cf49..620b81b 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -36,14 +36,19 @@
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
import android.credentials.IListEnabledProvidersCallback;
+import android.credentials.IRegisterCredentialDescriptionCallback;
import android.credentials.ISetEnabledProvidersCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
import android.credentials.ListEnabledProvidersResponse;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.ui.IntentFactory;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.BeginGetCredentialRequest;
@@ -59,9 +64,13 @@
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Entry point service for credential management.
@@ -75,6 +84,8 @@
CredentialManagerService, CredentialManagerServiceImpl> {
private static final String TAG = "CredManSysService";
+ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+ "enable_credential_description_api";
private final Context mContext;
@@ -164,6 +175,7 @@
if (services == null) {
return;
}
+
CredentialManagerServiceImpl serviceToBeRemoved = null;
for (CredentialManagerServiceImpl service : services) {
if (service != null) {
@@ -180,10 +192,14 @@
}
if (serviceToBeRemoved != null) {
removeServiceFromCache(serviceToBeRemoved, userId);
+ CredentialDescriptionRegistry.forUser(userId)
+ .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
}
// TODO("Iterate over system services and remove if needed")
}
+
+
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
int resolvedUserId) {
@@ -223,6 +239,53 @@
concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId));
return concatenatedServices;
}
+ public static boolean isCredentialDescriptionApiEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
+ }
+
+ @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
+ // to be guarded by 'service.mLock', which is the same as mLock.
+ private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
+ RequestSession session,
+ List<String> requestOptions, Set<ComponentName> activeCredentialContainers) {
+ List<ProviderSession> providerSessions = new ArrayList<>();
+ // Invoke all services of a user to initiate a provider session
+ runForUser((service) -> {
+ if (activeCredentialContainers.contains(service.getComponentName())) {
+ ProviderSession providerSession = service
+ .initiateProviderSessionForRequestLocked(session, requestOptions);
+ if (providerSession != null) {
+ providerSessions.add(providerSession);
+ }
+ }
+ });
+ return providerSessions;
+ }
+
+ @NonNull
+ private Set<String> getMatchingProviders(GetCredentialRequest request) {
+ // Session for active/provisioned credential descriptions;
+ CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
+ .forUser(UserHandle.getCallingUserId());
+
+ // All requested credential descriptions based on the given request.
+ Set<String> requestedCredentialDescriptions =
+ request.getGetCredentialOptions().stream().map(
+ getCredentialOption -> getCredentialOption
+ .getCredentialRetrievalData()
+ .getString(RegisterCredentialDescriptionRequest
+ .FLATTENED_REQUEST_STRING_KEY))
+ .collect(Collectors.toSet());
+
+ // All requested credential descriptions based on the given request.
+ return requestedCredentialDescriptions.stream()
+ .map(registry::filterCredentials)
+ .flatMap(
+ (Function<Set<String>, Stream<String>>)
+ Collection::stream)
+ .collect(Collectors.toSet());
+ }
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
// to be guarded by 'service.mLock', which is the same as mLock.
@@ -282,11 +345,11 @@
// Initiate all provider sessions
List<ProviderSession> providerSessions =
- initiateProviderSessions(
- session,
- request.getGetCredentialOptions().stream()
- .map(GetCredentialOption::getType)
- .collect(Collectors.toList()));
+ initiateProviderSessions(
+ session,
+ request.getGetCredentialOptions().stream()
+ .map(GetCredentialOption::getType)
+ .collect(Collectors.toList()));
if (providerSessions.isEmpty()) {
try {
@@ -316,7 +379,7 @@
ICreateCredentialCallback callback,
String callingPackage) {
Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
- // TODO : Implement cancellation
+
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
// New request session, scoped for this request only.
@@ -478,5 +541,77 @@
});
return cancelTransport;
}
+
+ @Override
+ public ICancellationSignal registerCredentialDescription(
+ RegisterCredentialDescriptionRequest request,
+ IRegisterCredentialDescriptionCallback callback, String callingPackage) {
+ Log.i(TAG, "registerCredentialDescription");
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+
+ List<CredentialProviderInfo> services =
+ CredentialProviderInfo.getAvailableServices(mContext,
+ UserHandle.getCallingUserId());
+
+ List<String> providers = services.stream()
+ .map(credentialProviderInfo
+ -> credentialProviderInfo.getServiceInfo().packageName).toList();
+ if (!providers.contains(callingPackage)) {
+ try {
+ callback.onError("UNKNOWN",
+ "Not an existing provider.");
+ } catch (RemoteException e) {
+ Log.i(
+ TAG,
+ "Issue invoking onError on IRegisterCredentialDescriptionCallback "
+ + "callback: "
+ + e.getMessage());
+ }
+ }
+
+ CredentialDescriptionRegistry session = CredentialDescriptionRegistry
+ .forUser(UserHandle.getCallingUserId());
+
+ session.executeRegisterRequest(request, callingPackage, callback);
+
+ return cancelTransport;
+ }
+
+ @Override
+ public ICancellationSignal unRegisterCredentialDescription(
+ UnregisterCredentialDescriptionRequest request,
+ IUnregisterCredentialDescriptionCallback callback, String callingPackage) {
+ Log.i(TAG, "registerCredentialDescription");
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+ List<CredentialProviderInfo> services =
+ CredentialProviderInfo.getAvailableServices(mContext,
+ UserHandle.getCallingUserId());
+
+ List<String> providers = services.stream()
+ .map(credentialProviderInfo
+ -> credentialProviderInfo.getServiceInfo().packageName).toList();
+
+ if (!providers.contains(callingPackage)) {
+ try {
+ callback.onError("UNKNOWN",
+ "Not an existing provider.");
+ } catch (RemoteException e) {
+ Log.i(
+ TAG,
+ "Issue invoking onError on IRegisterCredentialDescriptionCallback "
+ + "callback: "
+ + e.getMessage());
+ }
+ }
+
+ CredentialDescriptionRegistry session = CredentialDescriptionRegistry
+ .forUser(UserHandle.getCallingUserId());
+
+ session.executeUnregisterRequest(request, callingPackage, callback);
+
+ return cancelTransport;
+ }
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index a380636..797601a 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -64,8 +64,11 @@
} else {
Slog.i(TAG, "No selection found in UI result");
}
- } else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_CANCELED) {
- mCallbacks.onUiCancellation();
+ } else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED) {
+ mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
+ } else if (resultCode
+ == UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS) {
+ mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
}
}
@@ -75,8 +78,8 @@
public interface CredentialManagerUiCallback {
/** Called when the user makes a selection. */
void onUiSelection(UserSelectionDialogResult selection);
- /** Called when the user cancels the UI. */
- void onUiCancellation();
+ /** Called when the UI is canceled without a successful provider result. */
+ void onUiCancellation(boolean isUserCancellation);
}
public CredentialManagerUi(Context context, int userId,
CredentialManagerUiCallback callbacks) {
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index f7c5905..e3a27ec 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -124,9 +124,14 @@
}
@Override
- public void onUiCancellation() {
- respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED,
- "User cancelled the selector");
+ public void onUiCancellation(boolean isUserCancellation) {
+ if (isUserCancellation) {
+ respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED,
+ "User cancelled the selector");
+ } else {
+ respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED,
+ "The UI was interrupted - please try again.");
+ }
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 8e44f0f..f92ffe2 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -125,8 +125,8 @@
}
@Override // from CredentialManagerUiCallbacks
- public void onUiCancellation() {
- Log.i(TAG, "Ui canceled");
+ public void onUiCancellation(boolean isUserCancellation) {
+ Log.i(TAG, "Ui canceled. Canceled by user: " + isUserCancellation);
// User canceled the activity
finishSession();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ce67f3c..0963e3b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -30,6 +30,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -105,6 +106,7 @@
import static android.app.admin.DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER;
import static android.app.admin.DevicePolicyManager.STATUS_HAS_PAIRED;
+import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
@@ -140,6 +142,7 @@
import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
@@ -1092,13 +1095,13 @@
// (ACTION_DATE_CHANGED), or when manual clock adjustment is made
// (ACTION_TIME_CHANGED)
updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true);
- final int userId = getManagedUserId(mUserManager.getMainUser().getIdentifier());
+ final int userId = getManagedUserId(getMainUserId());
if (userId >= 0) {
updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
}
} else if (ACTION_PROFILE_OFF_DEADLINE.equals(action)) {
Slogf.i(LOG_TAG, "Profile off deadline alarm was triggered");
- final int userId = getManagedUserId(mUserManager.getMainUser().getIdentifier());
+ final int userId = getManagedUserId(getMainUserId());
if (userId >= 0) {
updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
} else {
@@ -2929,7 +2932,7 @@
final ActivityInfo ai = mInjector.binderWithCleanCallingIdentity(() -> {
try {
return mIPackageManager.getReceiverInfo(adminName,
- PackageManager.GET_META_DATA
+ GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
@@ -7957,13 +7960,11 @@
Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
- if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
- final ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(userId);
- return admin.mNearbyNotificationStreamingPolicy;
- }
+ final ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(userId);
+ return admin != null
+ ? admin.mNearbyNotificationStreamingPolicy
+ : NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
-
- return NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
@Override
@@ -7998,13 +7999,11 @@
Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
- if (mOwners.hasProfileOwner(userId) || mOwners.hasDeviceOwner()) {
- final ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(userId);
- return admin.mNearbyAppStreamingPolicy;
- }
+ final ActiveAdmin admin = getDeviceOrProfileOwnerAdminLocked(userId);
+ return admin != null
+ ? admin.mNearbyAppStreamingPolicy
+ : NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
-
- return NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
}
/**
@@ -8880,6 +8879,15 @@
}
}
+ private @UserIdInt int getMainUserId() {
+ UserHandle mainUser = mUserManager.getMainUser();
+ if (mainUser == null) {
+ Slogf.d(LOG_TAG, "getMainUserId(): no main user, returning USER_SYSTEM");
+ return UserHandle.USER_SYSTEM;
+ }
+ return mainUser.getIdentifier();
+ }
+
// TODO(b/240562946): Remove api as owner name is not used.
/**
* Returns the "name" of the device owner. It'll work for non-DO users too, but requires
@@ -14758,7 +14766,14 @@
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
- return checkProvisioningPreconditionSkipPermission(action, packageName, caller.getUserId());
+ long originalId = mInjector.binderClearCallingIdentity();
+ try {
+ return checkProvisioningPreconditionSkipPermission(
+ action, packageName, caller.getUserId());
+ } finally {
+ mInjector.binderRestoreCallingIdentity(originalId);
+ }
+
}
private int checkProvisioningPreconditionSkipPermission(String action,
String packageName, int userId) {
@@ -14824,22 +14839,31 @@
return STATUS_USER_HAS_PROFILE_OWNER;
}
- boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode();
- // System user is always running in headless system user mode.
- if (!isHeadlessSystemUserMode
- && !mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
+ if (!mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
return STATUS_USER_NOT_RUNNING;
}
if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
return STATUS_HAS_PAIRED;
}
+ boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode();
+
if (isHeadlessSystemUserMode) {
if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
Slogf.e(LOG_TAG, "In headless system user mode, "
+ "device owner can only be set on headless system user.");
return STATUS_NOT_SYSTEM_USER;
}
+
+ if (owner != null) {
+ DeviceAdminInfo adminInfo = findAdmin(
+ owner, deviceOwnerUserId, /* throwForMissingPermission= */ false);
+
+ if (adminInfo.getHeadlessDeviceOwnerMode()
+ != HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
+ return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
+ }
+ }
}
if (isAdb) {
@@ -18971,11 +18995,12 @@
"Provisioning preconditions failed with result: " + result);
}
onProvisionFullyManagedDeviceStarted(provisioningParams);
+
+ // These properties are global so will apply on all users
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
- final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
- ? UserHandle.USER_SYSTEM : caller.getUserId();
+ int deviceOwnerUserId = UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
deviceOwnerUserId,
provisioningParams.isLeaveAllSystemAppsEnabled(),
@@ -19101,9 +19126,12 @@
}
private void disallowAddUser() {
- if (mInjector.userManagerIsHeadlessSystemUserMode()) {
- Slogf.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
- return;
+ if (!isHeadlessFlagEnabled() || mIsAutomotive) {
+ // Auto still enables adding users due to the communal nature of those devices
+ if (mInjector.userManagerIsHeadlessSystemUserMode()) {
+ Slogf.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
+ return;
+ }
}
for (UserInfo userInfo : mUserManager.getUsers()) {
UserHandle userHandle = userInfo.getUserHandle();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3725756..b1cc306 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1446,6 +1446,9 @@
boolean isArc = context.getPackageManager().hasSystemFeature(
"org.chromium.arc");
+ boolean isTv = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LEANBACK);
+
boolean enableVrService = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index ae8dd41..0ca4dfc 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -1162,9 +1162,9 @@
private final int mUserId;
- // Conversation package name + shortcut ID -> Number of active notifications
+ // Conversation package name + shortcut ID -> Keys of active notifications
@GuardedBy("this")
- private final Map<Pair<String, String>, Integer> mActiveNotifCounts = new ArrayMap<>();
+ private final Map<Pair<String, String>, Set<String>> mActiveNotifKeys = new ArrayMap<>();
private NotificationListener(int userId) {
mUserId = userId;
@@ -1178,8 +1178,10 @@
String shortcutId = sbn.getNotification().getShortcutId();
PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
synchronized (this) {
- mActiveNotifCounts.merge(
- Pair.create(sbn.getPackageName(), shortcutId), 1, Integer::sum);
+ Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent(
+ Pair.create(sbn.getPackageName(), shortcutId),
+ (unusedKey) -> new HashSet<>());
+ notificationKeys.add(sbn.getKey());
}
});
@@ -1218,12 +1220,12 @@
Pair<String, String> conversationKey =
Pair.create(sbn.getPackageName(), shortcutId);
synchronized (this) {
- int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
- if (count <= 0) {
- mActiveNotifCounts.remove(conversationKey);
+ Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent(
+ conversationKey, (unusedKey) -> new HashSet<>());
+ notificationKeys.remove(sbn.getKey());
+ if (notificationKeys.isEmpty()) {
+ mActiveNotifKeys.remove(conversationKey);
cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS);
- } else {
- mActiveNotifCounts.put(conversationKey, count);
}
}
});
@@ -1289,7 +1291,7 @@
}
synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
- return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
+ return mActiveNotifKeys.containsKey(Pair.create(packageName, shortcutId));
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 694efbb..730cac9 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -440,7 +440,8 @@
Log.w(
LOG_TAG, "Ignoring permission $permissionName declared in system package" +
" $newPackageName: already declared in another system package" +
- " $oldPackageName")
+ " $oldPackageName"
+ )
return@forEachIndexed
}
} else {
@@ -516,15 +517,20 @@
if (packageState != null && androidPackage == null) {
return
}
- // TODO: STOPSHIP: We may need to retain permission definitions by disabled system packages
- // to retain their permission state.
-
+ val disabledSystemPackage = systemState.disabledSystemPackageStates[packageName]
+ ?.androidPackage
+ // Unlike in the previous implementation, we now also retain permission trees defined by
+ // disabled system packages for consistency with permissions.
val isPermissionTreeRemoved = systemState.permissionTrees.removeAllIndexed {
_, permissionTreeName, permissionTree ->
permissionTree.packageName == packageName && (
packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
it.isTree && it.name == permissionTreeName
}
+ ) && (
+ disabledSystemPackage?.permissions?.anyIndexed { _, it ->
+ it.isTree && it.name == permissionTreeName
+ } != true
)
}
if (isPermissionTreeRemoved) {
@@ -538,6 +544,10 @@
packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
!it.isTree && it.name == permissionName
}
+ ) && (
+ disabledSystemPackage?.permissions?.anyIndexed { _, it ->
+ !it.isTree && it.name == permissionName
+ } != true
)) {
// Different from the old implementation where we keep the permission state if the
// permission is declared by a disabled system package (ag/15189282), we now
@@ -574,8 +584,14 @@
private fun MutateStateScope.trimPermissionStates(appId: Int) {
val requestedPermissions = IndexedSet<String>()
forEachPackageInAppId(appId) {
+ // Note that we still trim the permission states requested by disabled system packages.
+ // Because in the previous implementation:
+ // despite revokeSharedUserPermissionsForLeavingPackageInternal() retains permissions
+ // requested by disabled system packages, revokeUnusedSharedUserPermissionsLocked(),
+ // which is call upon app update installation, didn't do such preservation.
+ // Hence, permissions only requested by disabled system packages were still trimmed in
+ // the previous implementation.
requestedPermissions += it.androidPackage!!.requestedPermissions
- // TODO: STOPSHIP: Retain permissions requested by disabled system packages.
}
newState.userStates.forEachIndexed { _, userId, userState ->
userState.uidPermissionFlags[appId]?.forEachReversedIndexed { _, permissionName, _ ->
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 70a5c3f..05a8b11 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -28,7 +28,7 @@
],
srcs: [
- "src/server/**/*.java",
+ "src/com/android/server/inputmethod/**/*.java",
],
static_libs: [
diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
new file mode 100644
index 0000000..77e32a7
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksInputMethodSystemServerTests",
+ "options": [
+ {"include-filter": "com.android.server.inputmethod"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ }
+ ]
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 73d04c6..720f486 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -65,19 +65,24 @@
@Test
public void testPerformShowIme() throws Exception {
- mVisibilityApplier.performShowIme(mWindowToken, null, null, SHOW_SOFT_INPUT);
+ synchronized (ImfLock.class) {
+ mVisibilityApplier.performShowIme(mWindowToken, null /* statsToken */,
+ InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT);
+ }
verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT);
}
@Test
public void testPerformHideIme() throws Exception {
- mVisibilityApplier.performHideIme(mWindowToken, null, null, HIDE_SOFT_INPUT);
+ synchronized (ImfLock.class) {
+ mVisibilityApplier.performHideIme(mWindowToken, null /* statsToken */, null,
+ HIDE_SOFT_INPUT);
+ }
verifyHideSoftInput(false, true);
}
@Test
public void testApplyImeVisibility_throwForInvalidState() {
- mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID);
assertThrows(IllegalArgumentException.class,
() -> mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID));
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index 8415fe1..a1b9b98 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -30,6 +30,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.inputmethod.InputMethodManager;
@@ -177,9 +178,28 @@
assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID);
}
- private void initImeTargetWindowState(IBinder windowToken) {
+ @Test
+ public void testComputeState_lastImeRequestedVisible_preserved_When_StateUnChanged() {
+ // Assume the last IME targeted window has requested IME visible
+ final IBinder lastImeTargetWindowToken = new Binder();
+ mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken;
+ mComputer.requestImeVisibility(lastImeTargetWindowToken, true);
+ final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull(
+ lastImeTargetWindowToken);
+ assertThat(lastState.isRequestedImeVisible()).isTrue();
+
+ // Verify when focusing the next window with STATE_UNCHANGED flag, the last IME
+ // visibility state will be preserved to the current window state.
+ final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(mWindowToken);
+ mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */);
+ assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo(
+ lastState.isRequestedImeVisible());
+ }
+
+ private ImeTargetWindowState initImeTargetWindowState(IBinder windowToken) {
final ImeTargetWindowState state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNCHANGED,
0, true, true, true);
mComputer.setWindowState(windowToken, state);
+ return state;
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 804bb49..dbdffd0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -71,6 +71,8 @@
/** Base class for testing {@link InputMethodManagerService}. */
public class InputMethodManagerServiceTestBase {
+ private static final int NO_VERIFY_SHOW_FLAGS = -1;
+
protected static final String TEST_SELECTED_IME_ID = "test.ime";
protected static final String TEST_EDITOR_PKG_NAME = "test.editor";
protected static final String TEST_FOCUSED_WINDOW_NAME = "test.editor/activity";
@@ -239,7 +241,7 @@
protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput)
throws RemoteException {
- verifyShowSoftInput(setVisible, showSoftInput, anyInt());
+ verifyShowSoftInput(setVisible, showSoftInput, NO_VERIFY_SHOW_FLAGS);
}
protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput, int showFlags)
@@ -249,7 +251,8 @@
.setCurrentMethodVisible();
}
verify(mMockInputMethod, times(showSoftInput ? 1 : 0))
- .showSoftInput(any(), any(), eq(showFlags), any());
+ .showSoftInput(any(), any(),
+ showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt(), any());
}
protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)
diff --git a/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/Android.bp
new file mode 100644
index 0000000..69b26cc
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/Android.bp
@@ -0,0 +1,15 @@
+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_test_helper_app {
+ name: "FrameworksServicesTests_install_target_sdk_22",
+ defaults: ["FrameworksServicesTests_apks_defaults"],
+
+ srcs: ["**/*.java"],
+}
diff --git a/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/AndroidManifest.xml
new file mode 100644
index 0000000..45cf769
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_target_sdk_22">
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="22">
+ </uses-sdk>
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/res/values/strings.xml
new file mode 100644
index 0000000..984152f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_22/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this placeholder file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="placeholder">placeholder</string>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/Android.bp
new file mode 100644
index 0000000..e3154db
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/Android.bp
@@ -0,0 +1,15 @@
+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_test_helper_app {
+ name: "FrameworksServicesTests_install_target_sdk_23",
+ defaults: ["FrameworksServicesTests_apks_defaults"],
+
+ srcs: ["**/*.java"],
+}
diff --git a/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/AndroidManifest.xml
new file mode 100644
index 0000000..725188b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_target_sdk_23">
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="23">
+ </uses-sdk>
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/res/values/strings.xml b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/res/values/strings.xml
new file mode 100644
index 0000000..984152f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/apks/install_target_sdk_23/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this placeholder file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="placeholder">placeholder</string>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index ebd6b64..f7efcd1 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -146,6 +146,8 @@
":FrameworksServicesTests_keyset_sb_ub",
":FrameworksServicesTests_keyset_splat_api",
":FrameworksServicesTests_keyset_splata_api",
+ ":FrameworksServicesTests_install_target_sdk_22",
+ ":FrameworksServicesTests_install_target_sdk_23",
],
out: ["PackageManagerServiceServerTests_apks_as_resources.res.zip"],
tools: ["soong_zip"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index 92bdd64..fd31b22 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -57,6 +57,7 @@
import android.os.StatFs;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.system.ErrnoException;
@@ -69,11 +70,10 @@
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
-import com.android.server.pm.test.service.server.R;
import com.android.internal.content.InstallLocationUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.test.service.server.R;
import dalvik.system.VMRuntime;
@@ -113,6 +113,9 @@
private static final int APP_INSTALL_SDCARD = InstallLocationUtils.APP_INSTALL_EXTERNAL;
+ private static final int DEFAULT_INSTALL_FLAGS =
+ PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
+
void failStr(String errMsg) {
Log.w(TAG, "errMsg=" + errMsg);
fail(errMsg);
@@ -1874,12 +1877,13 @@
private InstallParams replaceCerts(int apk1, int apk2, boolean cleanUp, boolean fail,
int retCode) throws Exception {
- int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ int rFlags = DEFAULT_INSTALL_FLAGS | PackageManager.INSTALL_REPLACE_EXISTING;
String apk1Name = "install1.apk";
String apk2Name = "install2.apk";
var pkg1 = getParsedPackage(apk1Name, apk1);
try {
- InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
+ InstallParams ip = installFromRawResource(apk1Name, apk1,
+ DEFAULT_INSTALL_FLAGS, false,
false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
installFromRawResource(apk2Name, apk2, rFlags, false,
fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1963,7 +1967,7 @@
InstallParams ip = replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, false, true,
PackageInstaller.STATUS_FAILURE_CONFLICT);
try {
- int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ int rFlags = DEFAULT_INSTALL_FLAGS | PackageManager.INSTALL_REPLACE_EXISTING;
installFromRawResource("install.apk", APP1_CERT1, rFlags, false,
false, -1,
PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -2466,7 +2470,8 @@
String apk1Name = "install1.apk";
String apk2Name = "install2.apk";
- final InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
+ final InstallParams ip = installFromRawResource(apk1Name, apk1,
+ DEFAULT_INSTALL_FLAGS, false,
false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
try {
PackageManager pm = mContext.getPackageManager();
@@ -2552,13 +2557,13 @@
// Clean up before testing first.
cleanUpInstall(pkg1.getPackageName());
cleanUpInstall(pkg2.getPackageName());
- installFromRawResource(apk1Name, apk1, 0, false, false, -1,
+ installFromRawResource(apk1Name, apk1, DEFAULT_INSTALL_FLAGS, false, false, -1,
PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
if (fail) {
- installFromRawResource(apk2Name, apk2, 0, false, true, retCode,
+ installFromRawResource(apk2Name, apk2, DEFAULT_INSTALL_FLAGS, false, true, retCode,
PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
} else {
- installFromRawResource(apk2Name, apk2, 0, false, false, -1,
+ installFromRawResource(apk2Name, apk2, DEFAULT_INSTALL_FLAGS, false, false, -1,
PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
// TODO: All checkSignatures tests should return the same result regardless of
// querying by package name or uid; however if there are any edge cases where
@@ -2638,7 +2643,7 @@
InstallParams ip1 = null;
try {
- ip1 = installFromRawResource(apk1Name, apk1, 0, false,
+ ip1 = installFromRawResource(apk1Name, apk1, DEFAULT_INSTALL_FLAGS, false,
false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
PackageManager pm = mContext.getPackageManager();
// Delete app2
@@ -2683,9 +2688,10 @@
int apk2 = SHARED2_CERT1_CERT2;
int rapk1 = SHARED1_CERT1;
boolean fail = true;
+ int flags = DEFAULT_INSTALL_FLAGS | PackageManager.INSTALL_REPLACE_EXISTING;
int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
- installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ installFromRawResource("install.apk", rapk1, flags, true,
fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
@@ -2695,9 +2701,10 @@
int apk2 = SHARED2_CERT1_CERT2;
int rapk2 = SHARED2_CERT1;
boolean fail = true;
+ int flags = DEFAULT_INSTALL_FLAGS | PackageManager.INSTALL_REPLACE_EXISTING;
int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
- installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ installFromRawResource("install.apk", rapk2, flags, true,
fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
@@ -2707,9 +2714,10 @@
int apk2 = SHARED2_CERT1;
int rapk1 = SHARED1_CERT2;
boolean fail = true;
+ int flags = DEFAULT_INSTALL_FLAGS | PackageManager.INSTALL_REPLACE_EXISTING;
int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
- installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ installFromRawResource("install.apk", rapk1, flags, true,
fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
@@ -2719,9 +2727,10 @@
int apk2 = SHARED2_CERT1;
int rapk2 = SHARED2_CERT2;
boolean fail = true;
+ int flags = DEFAULT_INSTALL_FLAGS | PackageManager.INSTALL_REPLACE_EXISTING;
int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
- installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ installFromRawResource("install.apk", rapk2, flags, true,
fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
@@ -2731,9 +2740,10 @@
int apk2 = SHARED2_CERT1;
int rapk1 = SHARED1_CERT1_CERT2;
boolean fail = true;
+ int flags = DEFAULT_INSTALL_FLAGS | PackageManager.INSTALL_REPLACE_EXISTING;
int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
- installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ installFromRawResource("install.apk", rapk1, flags, true,
fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
@@ -2743,9 +2753,10 @@
int apk2 = SHARED2_CERT1;
int rapk2 = SHARED2_CERT1_CERT2;
boolean fail = true;
+ int flags = DEFAULT_INSTALL_FLAGS | PackageManager.INSTALL_REPLACE_EXISTING;
int retCode = PackageInstaller.STATUS_FAILURE_CONFLICT;
checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
- installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ installFromRawResource("install.apk", rapk2, flags, true,
fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
@@ -2995,6 +3006,58 @@
getPm().registerDexModule(nonExistentApk, null);
}
+ @LargeTest
+ public void testMinInstallableTargetSdkPass() throws Exception {
+ // Test installing a package that meets the minimum installable sdk requirement
+ setMinInstallableTargetSdkFeatureFlags();
+ int flags = PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
+ installFromRawResource("install.apk", R.raw.install_target_sdk_23, flags,
+ true, false /* fail */, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+
+ @LargeTest
+ public void testMinInstallableTargetSdkFail() throws Exception {
+ // Test installing a package that doesn't meet the minimum installable sdk requirement
+ setMinInstallableTargetSdkFeatureFlags();
+ int flags = 0;
+ // Expect install to fail
+ installFromRawResource("install.apk", R.raw.install_target_sdk_22, flags,
+ true, true /* fail */, PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+
+ @LargeTest
+ public void testMinInstallableTargetSdkBypass() throws Exception {
+ // Test installing a package that doesn't meet the minimum installable sdk requirement
+ setMinInstallableTargetSdkFeatureFlags();
+ int flags = PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
+ installFromRawResource("install.apk", R.raw.install_target_sdk_22, flags,
+ true, false /* fail */, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+
+ private void setMinInstallableTargetSdkFeatureFlags() {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__install_block_enabled",
+ "true",
+ false);
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__min_installable_target_sdk",
+ "23",
+ false);
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__install_block_strict_mode_enabled",
+ "true",
+ false);
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__strict_mode_target_sdk",
+ "23",
+ false);
+ }
+
// Copied from com.android.server.pm.InstructionSets because we don't have access to it here.
private static String[] getAppDexInstructionSets(ApplicationInfo info) {
if (info.primaryCpuAbi != null) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 681bfcf..f915298 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -51,6 +51,7 @@
"kotlin-test",
"mockingservicestests-utils-mockito",
"mockito-target-extended-minus-junit4",
+ "platform-compat-test-rules",
"platform-test-annotations",
"service-blobstore",
"service-jobscheduler",
diff --git a/services/tests/servicestests/res/raw/backup_file_with_long_name b/services/tests/mockingservicestests/res/raw/backup_file_with_long_name
similarity index 100%
rename from services/tests/servicestests/res/raw/backup_file_with_long_name
rename to services/tests/mockingservicestests/res/raw/backup_file_with_long_name
Binary files differ
diff --git a/services/tests/servicestests/res/raw/backup_telephony_no_password b/services/tests/mockingservicestests/res/raw/backup_telephony_no_password
similarity index 100%
rename from services/tests/servicestests/res/raw/backup_telephony_no_password
rename to services/tests/mockingservicestests/res/raw/backup_telephony_no_password
Binary files differ
diff --git a/services/tests/servicestests/res/raw/backup_telephony_with_password b/services/tests/mockingservicestests/res/raw/backup_telephony_with_password
similarity index 100%
rename from services/tests/servicestests/res/raw/backup_telephony_with_password
rename to services/tests/mockingservicestests/res/raw/backup_telephony_with_password
Binary files differ
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 5f4ff1a..45fe795 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1191,31 +1191,6 @@
}
/**
- * Verify that we detect and ANR a wedged process when delivering a
- * broadcast with more than one priority tranche.
- */
- @Test
- public void testWedged_Registered_Prioritized() throws Exception {
- // Legacy stack doesn't detect these ANRs; likely an oversight
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
- final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
- final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN,
- ProcessBehavior.WEDGE);
- final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE,
- ProcessBehavior.NORMAL);
-
- final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
- List.of(makeRegisteredReceiver(receiverGreenApp, 10),
- makeRegisteredReceiver(receiverBlueApp, 5))));
-
- waitForIdle();
- verify(mAms).appNotResponding(eq(receiverGreenApp), any());
- verifyScheduleRegisteredReceiver(receiverBlueApp, airplane);
- }
-
- /**
* Verify that we handle registered receivers in a process that always
* responds with {@link DeadObjectException}, recovering to restart the
* process and deliver their next broadcast.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 05cad16..64e39ef 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -194,7 +194,7 @@
public void init_withDeviceConfigSetsParameters() {
// When the DeviceConfig already has a flag value stored (note this test will need to
// change if the default value changes from false).
- assertThat(CachedAppOptimizer.DEFAULT_USE_COMPACTION).isFalse();
+ assertThat(CachedAppOptimizer.DEFAULT_USE_COMPACTION).isTrue();
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -372,9 +372,8 @@
CachedAppOptimizer.KEY_USE_COMPACTION, "foobar", false);
assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- // Then we set the default.
- assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
- CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ // Invalid value is mapped to false
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(false);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index 3f16a98..3deb903 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -1048,8 +1048,7 @@
Consumer<Uri> consumer = invocation.getArgument(invocation.getArguments().length - 1);
consumer.accept(Uri.parse("a/b.png"));
return null;
- }).when(mMockScreenshotHelper).provideScreenshot(
- any(), any(), any(), anyInt(), anyInt(), any(), anyInt(), any(), any());
+ }).when(mMockScreenshotHelper).takeScreenshot(any(), any(), any());
mGameServiceProviderInstance.start();
startTask(taskId, GAME_A_MAIN_ACTIVITY);
mFakeGameService.requestCreateGameSession(taskId);
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupPasswordManagerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/BackupPasswordManagerTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/BackupPasswordManagerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/DataChangedJournalTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/DataChangedJournalTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/backup/DataChangedJournalTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/DataChangedJournalTest.java
index 9daa4ba..4b0b760 100644
--- a/services/tests/servicestests/src/com/android/server/backup/DataChangedJournalTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/DataChangedJournalTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.server.backup;
diff --git a/services/tests/servicestests/src/com/android/server/backup/ProcessedPackagesJournalTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/ProcessedPackagesJournalTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/ProcessedPackagesJournalTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/ProcessedPackagesJournalTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
index 5800aca..2c5ae37 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
diff --git a/services/tests/servicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java
index 00c6391..4d6845c 100644
--- a/services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java
@@ -25,7 +25,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.mockingservicestests.R;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/mockingservicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
index 48b0aad..6093f4b 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java
@@ -58,7 +58,7 @@
@RunWith(AndroidJUnit4.class)
public class BackupEligibilityRulesTest {
private static final String CUSTOM_BACKUP_AGENT_NAME = "custom.backup.agent";
- private static final String TEST_PACKAGE_NAME = "com.android.frameworks.servicestests";
+ private static final String TEST_PACKAGE_NAME = "com.android.frameworks.mockingservicestests";
private static final Signature SIGNATURE_1 = generateSignature((byte) 1);
private static final Signature SIGNATURE_2 = generateSignature((byte) 2);
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupObserverUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupObserverUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/utils/BackupObserverUtilsTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupObserverUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/DataStreamFileCodecTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/DataStreamFileCodecTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/utils/DataStreamFileCodecTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/DataStreamFileCodecTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/FileUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FileUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/utils/FileUtilsTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/FileUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupRestoreObserverUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupRestoreObserverUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/utils/FullBackupRestoreObserverUtilsTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupRestoreObserverUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/RandomAccessFileUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/RandomAccessFileUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/utils/RandomAccessFileUtilsTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/RandomAccessFileUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/SparseArrayUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/SparseArrayUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/backup/utils/SparseArrayUtilsTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/SparseArrayUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index e2536f8..30c6975 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -51,7 +51,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.mockingservicestests.R;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.restore.PerformAdbRestoreTask;
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 2a790a1..f2cba40 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -80,8 +80,6 @@
@Mock
private DisplayBlanker mDisplayBlankerMock;
@Mock
- private HighBrightnessModeMetadata mHighBrightnessModeMetadataMock;
- @Mock
private LogicalDisplay mLogicalDisplayMock;
@Mock
private DisplayDevice mDisplayDeviceMock;
@@ -171,7 +169,7 @@
mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
+ });
when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index d99ed78..4f8cb88 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -84,8 +84,6 @@
@Mock
private DisplayDevice mDisplayDeviceMock;
@Mock
- private HighBrightnessModeMetadata mHighBrightnessModeMetadataMock;
- @Mock
private BrightnessTracker mBrightnessTrackerMock;
@Mock
private BrightnessSetting mBrightnessSettingMock;
@@ -153,7 +151,7 @@
mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
+ });
when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 7e1a42b..ddb6f23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -30,6 +30,7 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_UI;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -855,6 +856,9 @@
case WORK_TYPE_FGS:
workTypeString = "fgs";
break;
+ case WORK_TYPE_UI:
+ workTypeString = "ui";
+ break;
case WORK_TYPE_EJ:
workTypeString = "ej";
break;
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 19b5ad6..de54537 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -427,7 +427,8 @@
doReturn(true).when(mService)
.bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
- doNothing().when(mService).generateCrop(wallpaper);
+ spyOn(mService.mWallpaperCropper);
+ doNothing().when(mService.mWallpaperCropper).generateCrop(wallpaper);
// timestamps of {ACTION_WALLPAPER_CHANGED, onWallpaperColorsChanged}
final long[] timestamps = new long[2];
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 80305fa..ced2a4b 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -60,7 +60,6 @@
"truth-prebuilt",
"junit",
"junit-params",
- "platform-compat-test-rules",
"ActivityContext",
"coretests-aidl",
],
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 450cc40..19857ed 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -36,6 +36,8 @@
useParentsContacts='false'
crossProfileIntentFilterAccessControl='20'
crossProfileIntentResolutionStrategy='0'
+ mediaSharedWithParent='true'
+ credentialSharableWithParent='false'
/>
</profile-type>
<profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 13d93cb..d9461aa 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -17,7 +17,6 @@
package com.android.server.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
@@ -305,9 +304,7 @@
mSystemActionPerformer.performSystemAction(
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
verify(mMockScreenshotHelper).takeScreenshot(
- eq(TAKE_SCREENSHOT_FULLSCREEN),
- eq(SCREENSHOT_ACCESSIBILITY_ACTIONS),
- any(Handler.class), any());
+ eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
}
// PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/backup/OWNERS b/services/tests/servicestests/src/com/android/server/backup/OWNERS
deleted file mode 100644
index d99779e..0000000
--- a/services/tests/servicestests/src/com/android/server/backup/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/backup/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
new file mode 100644
index 0000000..c7fb97f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.server.companion.datatransfer.contextsync;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.Presubmit;
+import android.telecom.Call;
+import android.telecom.ParcelableCall;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+public class CrossDeviceCallTest {
+
+ private static final String CALLER_DISPLAY_NAME = "name";
+ private static final String CONTACT_DISPLAY_NAME = "contact";
+
+ @Test
+ public void updateCallDetails_uninitialized() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.UNKNOWN_STATUS);
+ assertWithMessage("Wrong controls").that(crossDeviceCall.getControls()).isEmpty();
+ }
+
+ @Test
+ public void updateCallDetails_ringing() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.RINGING);
+ assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
+ android.companion.Telecom.Call.REJECT,
+ android.companion.Telecom.Call.SILENCE));
+ }
+
+ @Test
+ public void updateCallDetails_ongoing() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.ONGOING);
+ assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.END,
+ android.companion.Telecom.Call.MUTE,
+ android.companion.Telecom.Call.PUT_ON_HOLD));
+ }
+
+ @Test
+ public void updateCallDetails_holding() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_HOLDING,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.ON_HOLD);
+ assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.END,
+ android.companion.Telecom.Call.TAKE_OFF_HOLD));
+ }
+
+ @Test
+ public void updateCallDetails_cannotHold() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, Call.Details.CAPABILITY_MUTE));
+ assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.ONGOING);
+ assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.END,
+ android.companion.Telecom.Call.MUTE));
+ }
+
+ @Test
+ public void updateCallDetails_cannotMute() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, Call.Details.CAPABILITY_HOLD));
+ assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.ONGOING);
+ assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.END,
+ android.companion.Telecom.Call.PUT_ON_HOLD));
+ }
+
+ @Test
+ public void updateCallDetails_transitionRingingToOngoing() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ assertWithMessage("Wrong status for ringing state").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.RINGING);
+ assertWithMessage("Wrong controls for ringing state").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
+ android.companion.Telecom.Call.REJECT,
+ android.companion.Telecom.Call.SILENCE));
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ assertWithMessage("Wrong status for active state").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.ONGOING);
+ assertWithMessage("Wrong controls for active state").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.END,
+ android.companion.Telecom.Call.MUTE,
+ android.companion.Telecom.Call.PUT_ON_HOLD));
+ }
+
+ @Test
+ public void updateSilencedIfRinging_ringing_silenced() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ crossDeviceCall.updateSilencedIfRinging();
+ assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.RINGING_SILENCED);
+ assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
+ android.companion.Telecom.Call.REJECT));
+ }
+
+ @Test
+ public void updateSilencedIfRinging_notRinging_notSilenced() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ crossDeviceCall.updateSilencedIfRinging();
+ assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.ONGOING);
+ assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.Call.END,
+ android.companion.Telecom.Call.MUTE,
+ android.companion.Telecom.Call.PUT_ON_HOLD));
+ }
+
+ @Test
+ public void getReadableCallerId_enterpriseCall_adminBlocked_ott() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = true;
+ crossDeviceCall.mIsOtt = true;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
+
+ final String result = crossDeviceCall.getReadableCallerId(true);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CALLER_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_enterpriseCall_adminUnblocked_ott() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = true;
+ crossDeviceCall.mIsOtt = true;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
+
+ final String result = crossDeviceCall.getReadableCallerId(false);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CALLER_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_enterpriseCall_adminBlocked_pstn() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = true;
+ crossDeviceCall.mIsOtt = false;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
+
+ final String result = crossDeviceCall.getReadableCallerId(true);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CALLER_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_nonEnterpriseCall_adminBlocked_ott() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = false;
+ crossDeviceCall.mIsOtt = true;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
+
+ final String result = crossDeviceCall.getReadableCallerId(true);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CALLER_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_ott() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = false;
+ crossDeviceCall.mIsOtt = true;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
+
+ final String result = crossDeviceCall.getReadableCallerId(false);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CALLER_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_nonEnterpriseCall_adminBlocked_pstn() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = false;
+ crossDeviceCall.mIsOtt = false;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
+
+ final String result = crossDeviceCall.getReadableCallerId(true);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CONTACT_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_pstn() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext().getPackageManager(), /* call= */
+ null, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = false;
+ crossDeviceCall.mIsOtt = false;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
+
+ final String result = crossDeviceCall.getReadableCallerId(false);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CONTACT_DISPLAY_NAME);
+ }
+
+ private Call.Details createCallDetails(int state, int capabilities) {
+ final ParcelableCall.ParcelableCallBuilder parcelableCallBuilder =
+ new ParcelableCall.ParcelableCallBuilder();
+ parcelableCallBuilder.setCallerDisplayName(CALLER_DISPLAY_NAME);
+ parcelableCallBuilder.setContactDisplayName(CONTACT_DISPLAY_NAME);
+ parcelableCallBuilder.setCapabilities(capabilities);
+ parcelableCallBuilder.setState(state);
+ parcelableCallBuilder.setConferenceableCallIds(Collections.emptyList());
+ return Call.Details.createFromParcelableCall(parcelableCallBuilder.createParcelableCall());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 00d4a6d..dad7977 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -51,6 +51,7 @@
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -75,6 +76,7 @@
import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
+import android.media.AudioManager;
import android.net.MacAddress;
import android.net.Uri;
import android.os.Binder;
@@ -223,6 +225,8 @@
@Mock
private IVirtualDeviceActivityListener mActivityListener;
@Mock
+ private IVirtualDeviceSoundEffectListener mSoundEffectListener;
+ @Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@Mock
private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
@@ -630,6 +634,8 @@
@Test
public void onAppsOnVirtualDeviceChanged_multipleVirtualDevices_listenersNotified() {
+ createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_2);
+
ArraySet<Integer> uidsOnDevice1 = new ArraySet<>(Arrays.asList(UID_1, UID_2));
ArraySet<Integer> uidsOnDevice2 = new ArraySet<>(Arrays.asList(UID_3, UID_4));
mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
@@ -641,7 +647,7 @@
new ArraySet<>(Arrays.asList(UID_1, UID_2)));
// Notifies that the running apps on the second virtual device has changed.
- mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId() + 1, uidsOnDevice2);
+ mVdms.notifyRunningAppsChanged(VIRTUAL_DEVICE_ID_2, uidsOnDevice2);
TestableLooper.get(this).processAllMessages();
// The union of the apps running on both virtual devices are sent to the listeners.
verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
@@ -1055,6 +1061,16 @@
}
@Test
+ public void closedDevice_lateCallToRunningAppsChanged_isIgnored() {
+ mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
+ int deviceId = mDeviceImpl.getDeviceId();
+ mDeviceImpl.close();
+ mVdms.notifyRunningAppsChanged(deviceId, Sets.newArraySet(UID_1));
+ TestableLooper.get(this).processAllMessages();
+ verify(mAppsOnVirtualDeviceListener, never()).onAppsOnAnyVirtualDeviceChanged(any());
+ }
+
+ @Test
public void sendKeyEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
@@ -1572,6 +1588,13 @@
intent.filterEquals(blockedAppIntent)), any(), any());
}
+ @Test
+ public void playSoundEffect_callsSoundEffectListener() throws Exception {
+ mVdm.playSoundEffect(mDeviceImpl.getDeviceId(), AudioManager.FX_KEY_CLICK);
+
+ verify(mSoundEffectListener).onPlaySoundEffect(AudioManager.FX_KEY_CLICK);
+ }
+
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
VirtualDeviceParams params = new VirtualDeviceParams.Builder()
.setBlockedActivities(getBlockedActivities())
@@ -1585,7 +1608,8 @@
mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
mInputController, mSensorController, mCameraAccessController,
/* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
- mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+ mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
+ mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(virtualDeviceImpl);
return virtualDeviceImpl;
}
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 210aeef..4998a6c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1085,27 +1085,6 @@
// DPMS.getApplicationLabel() because Context.createPackageContextAsUser() is not mockable.
}
- /**
- * TODO(b/174859111): move to automotive-only section
- * Test for {@link DevicePolicyManager#setDeviceOwner} in headless system user mode.
- */
- @Test
- public void testSetDeviceOwner_headlessSystemUserMode() throws Exception {
- when(getServices().userManagerForMock.isHeadlessSystemUserMode()).thenReturn(true);
- setDeviceOwner_headlessSystemUser();
-
- // Try to set a profile owner on the same user, which should fail.
- setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
- dpm.setActiveAdmin(admin2, /* refreshing= */ true, CALLER_USER_HANDLE);
- assertExpectException(IllegalStateException.class,
- /* messageRegex= */ "profile owner is already set",
- () -> dpm.setProfileOwner(admin2, CALLER_USER_HANDLE));
-
- // DO admin can't be deactivated.
- dpm.removeActiveAdmin(admin1);
- assertThat(dpm.isAdminActive(admin1)).isTrue();
- }
-
private void setDeviceOwner() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index a45144e..82f6493 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -67,6 +67,9 @@
new DeviceState(0, "DEFAULT", 0 /* flags */);
private static final DeviceState OTHER_DEVICE_STATE =
new DeviceState(1, "OTHER", 0 /* flags */);
+ private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+ new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP",
+ DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */);
// A device state that is not reported as being supported for the default test provider.
private static final DeviceState UNSUPPORTED_DEVICE_STATE =
new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
@@ -77,6 +80,7 @@
private TestDeviceStateProvider mProvider;
private DeviceStateManagerService mService;
private TestSystemPropertySetter mSysPropSetter;
+ private WindowProcessController mWindowProcessController;
@Before
public void setup() {
@@ -88,10 +92,10 @@
// Necessary to allow us to check for top app process id in tests
mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
- WindowProcessController windowProcessController = mock(WindowProcessController.class);
+ mWindowProcessController = mock(WindowProcessController.class);
when(mService.mActivityTaskManagerInternal.getTopApp())
- .thenReturn(windowProcessController);
- when(windowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
+ .thenReturn(mWindowProcessController);
+ when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
flushHandler(); // Flush the handler to ensure the initial values are committed.
}
@@ -201,7 +205,7 @@
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE);
+ OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE});
flushHandler();
@@ -234,10 +238,10 @@
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE);
+ OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE});
+ OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP});
flushHandler();
// The current committed and requests states do not change because the current state remains
@@ -248,7 +252,7 @@
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE);
+ OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
// The callback wasn't notified about a change in supported states as the states have not
// changed.
@@ -261,7 +265,8 @@
assertNotNull(info);
assertArrayEquals(info.supportedStates,
new int[] { DEFAULT_DEVICE_STATE.getIdentifier(),
- OTHER_DEVICE_STATE.getIdentifier() });
+ OTHER_DEVICE_STATE.getIdentifier(),
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier()});
assertEquals(info.baseState, DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier());
}
@@ -513,6 +518,54 @@
OTHER_DEVICE_STATE.getIdentifier());
}
+ @Test
+ public void requestState_flagCancelWhenRequesterNotOnTop_onDeviceSleep()
+ throws RemoteException {
+ requestState_flagCancelWhenRequesterNotOnTop_common(
+ // When the device is awake, the state should not change
+ () -> mService.mOverrideRequestScreenObserver.onAwakeStateChanged(true),
+ // When the device is in sleep mode, the state should be canceled
+ () -> mService.mOverrideRequestScreenObserver.onAwakeStateChanged(false)
+ );
+ }
+
+ @Test
+ public void requestState_flagCancelWhenRequesterNotOnTop_onKeyguardShow()
+ throws RemoteException {
+ requestState_flagCancelWhenRequesterNotOnTop_common(
+ // When the keyguard is not showing, the state should not change
+ () -> mService.mOverrideRequestScreenObserver.onKeyguardStateChanged(false),
+ // When the keyguard is showing, the state should be canceled
+ () -> mService.mOverrideRequestScreenObserver.onKeyguardStateChanged(true)
+ );
+ }
+
+ @Test
+ public void requestState_flagCancelWhenRequesterNotOnTop_onTaskStackChanged()
+ throws RemoteException {
+ requestState_flagCancelWhenRequesterNotOnTop_common(
+ // When the app is foreground, the state should not change
+ () -> {
+ int pid = Binder.getCallingPid();
+ when(mWindowProcessController.getPid()).thenReturn(pid);
+ try {
+ mService.mOverrideRequestTaskStackListener.onTaskStackChanged();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ },
+ // When the app is not foreground, the state should change
+ () -> {
+ when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
+ try {
+ mService.mOverrideRequestTaskStackListener.onTaskStackChanged();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ );
+ }
+
@FlakyTest(bugId = 200332057)
@Test
public void requestState_becomesUnsupported() throws RemoteException {
@@ -743,6 +796,84 @@
Assert.assertTrue(Arrays.equals(expected, actual));
}
+ /**
+ * Common code to verify the handling of FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP flag.
+ *
+ * The device state with FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP should be automatically canceled
+ * when certain events happen, e.g. when the top activity belongs to another app or when the
+ * device goes into the sleep mode.
+ *
+ * @param noChangeEvent an event that should not trigger auto cancellation of the state.
+ * @param autoCancelEvent an event that should trigger auto cancellation of the state.
+ * @throws RemoteException when the service throws exceptions.
+ */
+ private void requestState_flagCancelWhenRequesterNotOnTop_common(
+ Runnable noChangeEvent,
+ Runnable autoCancelEvent
+ ) throws RemoteException {
+ TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ mService.getBinderService().registerCallback(callback);
+ flushHandler();
+
+ final IBinder token = new Binder();
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+ mService.getBinderService().requestState(token,
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier(),
+ 0 /* flags */);
+ flushHandler(2 /* count */);
+
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_ACTIVE);
+
+ // Committed state changes as there is a requested override.
+ assertDeviceStateConditions(
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+ DEFAULT_DEVICE_STATE, /* base state */
+ true /* isOverrideState */);
+
+ noChangeEvent.run();
+ flushHandler();
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertDeviceStateConditions(
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+ DEFAULT_DEVICE_STATE, /* base state */
+ true /* isOverrideState */);
+
+ autoCancelEvent.run();
+ flushHandler();
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertDeviceStateConditions(DEFAULT_DEVICE_STATE, DEFAULT_DEVICE_STATE,
+ false /* isOverrideState */);
+ }
+
+ /**
+ * Verify that the current device state and base state match the expected values.
+ *
+ * @param state the expected committed state.
+ * @param baseState the expected base state.
+ * @param isOverrideState whether a state override is active.
+ */
+ private void assertDeviceStateConditions(
+ DeviceState state, DeviceState baseState, boolean isOverrideState) {
+ assertEquals(mService.getCommittedState(), Optional.of(state));
+ assertEquals(mService.getBaseState(), Optional.of(baseState));
+ assertEquals(mSysPropSetter.getValue(),
+ state.getIdentifier() + ":" + state.getName());
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ state.getIdentifier());
+ if (isOverrideState) {
+ // When a state override is active, the committed state should batch the override state.
+ assertEquals(mService.getOverrideState().get(), state);
+ } else {
+ // When there is no state override, the override state should be empty.
+ assertFalse(mService.getOverrideState().isPresent());
+ }
+ }
+
private static final class TestDeviceStatePolicy extends DeviceStatePolicy {
private final DeviceStateProvider mProvider;
private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
@@ -801,8 +932,10 @@
}
private static final class TestDeviceStateProvider implements DeviceStateProvider {
- private DeviceState[] mSupportedDeviceStates = new DeviceState[]{ DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE };
+ private DeviceState[] mSupportedDeviceStates = new DeviceState[]{
+ DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE,
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
private Listener mListener;
@Override
diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
deleted file mode 100644
index 24fc348..0000000
--- a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
+++ /dev/null
@@ -1,57 +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.server.display;
-
-import static org.junit.Assert.assertEquals;
-
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class HbmEventTest {
- private long mStartTimeMillis;
- private long mEndTimeMillis;
- private HbmEvent mHbmEvent;
-
- @Before
- public void setUp() {
- mStartTimeMillis = 10;
- mEndTimeMillis = 20;
- mHbmEvent = new HbmEvent(mStartTimeMillis, mEndTimeMillis);
- }
-
- @Test
- public void getCorrectValues() {
- assertEquals(mHbmEvent.getStartTimeMillis(), mStartTimeMillis);
- assertEquals(mHbmEvent.getEndTimeMillis(), mEndTimeMillis);
- }
-
- @Test
- public void toStringGeneratesExpectedString() {
- String actualString = mHbmEvent.toString();
- String expectedString = "HbmEvent: {startTimeMillis:" + mStartTimeMillis
- + ", endTimeMillis: " + mEndTimeMillis + "}, total: "
- + ((mEndTimeMillis - mStartTimeMillis) / 1000) + "]";
- assertEquals(actualString, expectedString);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 2655c3f..a1e5ce7 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -96,7 +96,6 @@
private Binder mDisplayToken;
private String mDisplayUniqueId;
private Context mContextSpy;
- private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@@ -119,7 +118,6 @@
mTestLooper = new TestLooper(mClock::now);
mDisplayToken = null;
mDisplayUniqueId = "unique_id";
-
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(resolver);
@@ -136,8 +134,7 @@
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
- mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
- null, mContextSpy);
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@@ -147,8 +144,7 @@
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
- mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
- null, mContextSpy);
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy);
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
@@ -703,12 +699,9 @@
// Creates instance with standard initialization values.
private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
initHandler(clock);
- if (mHighBrightnessModeMetadata == null) {
- mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
- }
return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
- DEFAULT_HBM_DATA, null, () -> {}, mHighBrightnessModeMetadata, mContextSpy);
+ DEFAULT_HBM_DATA, null, () -> {}, mContextSpy);
}
private void initHandler(OffsettableClock clock) {
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
deleted file mode 100644
index ede54e0..0000000
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
+++ /dev/null
@@ -1,59 +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.server.display;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class HighBrightnessModeMetadataTest {
- private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
-
- private long mRunningStartTimeMillis = -1;
-
- @Before
- public void setUp() {
- mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
- }
-
- @Test
- public void checkDefaultValues() {
- assertEquals(mHighBrightnessModeMetadata.getRunningStartTimeMillis(),
- mRunningStartTimeMillis);
- assertEquals(mHighBrightnessModeMetadata.getHbmEventQueue().size(), 0);
- }
-
- @Test
- public void checkSetValues() {
- mRunningStartTimeMillis = 10;
- mHighBrightnessModeMetadata.setRunningStartTimeMillis(mRunningStartTimeMillis);
- assertEquals(mHighBrightnessModeMetadata.getRunningStartTimeMillis(),
- mRunningStartTimeMillis);
- HbmEvent expectedHbmEvent = new HbmEvent(10, 20);
- mHighBrightnessModeMetadata.addHbmEvent(expectedHbmEvent);
- HbmEvent actualHbmEvent = mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst();
- assertEquals(expectedHbmEvent.toString(), actualHbmEvent.toString());
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index a9b7143..5ef762b 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -691,6 +691,7 @@
// 2) Mark the displays as STATE_OFF so that it can continue with transition
// 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
// 4) Dispatch handler events.
+ mLogicalDisplayMapper.onBootCompleted();
mLogicalDisplayMapper.setDeviceStateLocked(0, false);
mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
advanceTime(1000);
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index cbeaf7b..c24d83f 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -16,18 +16,26 @@
package com.android.server.display.brightness;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.PowerManager;
import android.view.Display;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.BrightnessSetting;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
import org.junit.Before;
import org.junit.Test;
@@ -39,11 +47,16 @@
@RunWith(AndroidJUnit4.class)
public final class DisplayBrightnessControllerTest {
private static final int DISPLAY_ID = 1;
+ private static final float DEFAULT_BRIGHTNESS = 0.4f;
@Mock
private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
@Mock
private Context mContext;
+ @Mock
+ private BrightnessSetting mBrightnessSetting;
+ @Mock
+ private Runnable mOnBrightnessChangeRunnable;
private DisplayBrightnessController mDisplayBrightnessController;
@@ -58,11 +71,11 @@
}
};
mDisplayBrightnessController = new DisplayBrightnessController(mContext, injector,
- DISPLAY_ID);
+ DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable);
}
@Test
- public void updateBrightnessWorksAsExpected() {
+ public void updateBrightness() {
DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class);
DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
int targetDisplayState = Display.STATE_DOZE;
@@ -70,6 +83,8 @@
targetDisplayState)).thenReturn(displayBrightnessStrategy);
mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
+ assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategyLocked(),
+ displayBrightnessStrategy);
}
@Test
@@ -77,4 +92,154 @@
mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
verify(mDisplayBrightnessStrategySelector).isAllowAutoBrightnessWhileDozingConfig();
}
+
+ @Test
+ public void setTemporaryBrightness() {
+ float temporaryBrightness = 0.4f;
+ TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
+ TemporaryBrightnessStrategy.class);
+ when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
+ temporaryBrightnessStrategy);
+ mDisplayBrightnessController.setTemporaryBrightness(temporaryBrightness);
+ verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(temporaryBrightness);
+ }
+
+ @Test
+ public void setCurrentScreenBrightness() {
+ // Current Screen brightness is set as expected when a different value than the current
+ // is set
+ float currentScreenBrightness = 0.4f;
+ mDisplayBrightnessController.setCurrentScreenBrightness(currentScreenBrightness);
+ assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
+ currentScreenBrightness, 0.0f);
+ verify(mOnBrightnessChangeRunnable).run();
+
+ // No change to the current screen brightness is same as the existing one
+ mDisplayBrightnessController.setCurrentScreenBrightness(currentScreenBrightness);
+ verifyNoMoreInteractions(mOnBrightnessChangeRunnable);
+ }
+
+ @Test
+ public void setPendingScreenBrightnessSetting() {
+ float pendingScreenBrightness = 0.4f;
+ mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
+ assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+ pendingScreenBrightness, 0.0f);
+ }
+
+ @Test
+ public void updateUserSetScreenBrightness() {
+ // No brightness is set if the pending brightness is invalid
+ mDisplayBrightnessController.setPendingScreenBrightness(Float.NaN);
+ assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+
+ // user set brightness is not set if the current and the pending brightness are same.
+ float currentBrightness = 0.4f;
+ TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
+ TemporaryBrightnessStrategy.class);
+ when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
+ temporaryBrightnessStrategy);
+ mDisplayBrightnessController.setCurrentScreenBrightness(currentBrightness);
+ mDisplayBrightnessController.setPendingScreenBrightness(currentBrightness);
+ mDisplayBrightnessController.setTemporaryBrightness(currentBrightness);
+ assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+ verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, 0.0f);
+
+ // user set brightness is set as expected
+ currentBrightness = 0.4f;
+ float pendingScreenBrightness = 0.3f;
+ float temporaryScreenBrightness = 0.2f;
+ mDisplayBrightnessController.setCurrentScreenBrightness(currentBrightness);
+ mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
+ mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
+ assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
+ assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
+ pendingScreenBrightness, 0.0f);
+ assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+ pendingScreenBrightness, 0.0f);
+ verify(mOnBrightnessChangeRunnable, times(2)).run();
+ verify(temporaryBrightnessStrategy, times(2))
+ .setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, 0.0f);
+ }
+
+ @Test
+ public void registerBrightnessSettingChangeListener() {
+ BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
+ BrightnessSetting.BrightnessSettingListener.class);
+ mDisplayBrightnessController.registerBrightnessSettingChangeListener(
+ brightnessSettingListener);
+ verify(mBrightnessSetting).registerListener(brightnessSettingListener);
+ assertEquals(mDisplayBrightnessController.getBrightnessSettingListenerLocked(),
+ brightnessSettingListener);
+ }
+
+ @Test
+ public void getScreenBrightnessSetting() {
+ // getScreenBrightnessSetting returns the value relayed by BrightnessSetting, if the
+ // valid is valid and in range
+ float brightnessSetting = 0.2f;
+ when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+ assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), brightnessSetting,
+ 0.0f);
+
+ // getScreenBrightnessSetting value is clamped if BrightnessSetting returns value beyond max
+ brightnessSetting = 1.1f;
+ when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+ assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), 1.0f,
+ 0.0f);
+
+ // getScreenBrightnessSetting returns default value is BrightnessSetting returns invalid
+ // value.
+ brightnessSetting = Float.NaN;
+ when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+ assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), DEFAULT_BRIGHTNESS,
+ 0.0f);
+ }
+
+ @Test
+ public void setBrightnessSetsInBrightnessSetting() {
+ float brightnessValue = 0.3f;
+ mDisplayBrightnessController.setBrightness(brightnessValue);
+ verify(mBrightnessSetting).setBrightness(brightnessValue);
+ }
+
+ @Test
+ public void updateScreenBrightnessSetting() {
+ // This interaction happens in the constructor itself
+ verify(mBrightnessSetting).getBrightness();
+
+ // Sets the appropriate value when valid, and not equal to the current brightness
+ float brightnessValue = 0.3f;
+ mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+ assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue,
+ 0.0f);
+ verify(mOnBrightnessChangeRunnable).run();
+ verify(mBrightnessSetting).setBrightness(brightnessValue);
+
+ // Does nothing if the value is invalid
+ mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
+ verifyNoMoreInteractions(mOnBrightnessChangeRunnable, mBrightnessSetting);
+
+ // Does nothing if the value is same as the current brightness
+ brightnessValue = 0.2f;
+ mDisplayBrightnessController.setCurrentScreenBrightness(brightnessValue);
+ verify(mOnBrightnessChangeRunnable, times(2)).run();
+ mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+ verifyNoMoreInteractions(mOnBrightnessChangeRunnable, mBrightnessSetting);
+ }
+
+ @Test
+ public void stop() {
+ BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
+ BrightnessSetting.BrightnessSettingListener.class);
+ mDisplayBrightnessController.registerBrightnessSettingChangeListener(
+ brightnessSettingListener);
+ mDisplayBrightnessController.stop();
+ verify(mBrightnessSetting).unregisterListener(brightnessSettingListener);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 3ce747f..e1a04ad5 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -25,6 +25,7 @@
import android.hardware.BatteryState.STATUS_DISCHARGING
import android.hardware.BatteryState.STATUS_FULL
import android.hardware.BatteryState.STATUS_UNKNOWN
+import android.hardware.input.HostUsiVersion
import android.hardware.input.IInputDeviceBatteryListener
import android.hardware.input.IInputDeviceBatteryState
import android.hardware.input.IInputDevicesChangedListener
@@ -86,7 +87,7 @@
.setDescriptor("descriptor $deviceId")
.setExternal(true)
.setHasBattery(hasBattery)
- .setSupportsUsi(supportsUsi)
+ .setUsiVersion(if (supportsUsi) HostUsiVersion(1, 0) else null)
.setGeneration(generation)
.build()
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 0fd6a9e..a0f03bb 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -22,7 +22,6 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
import static com.android.server.job.JobConcurrencyManager.workTypeToString;
@@ -59,7 +58,8 @@
private static final double[] EQUAL_PROBABILITY_CDF =
buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
- 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES);
+ 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
+ 1.0 / NUM_WORK_TYPES);
private Random mRandom;
private WorkCountTracker mWorkCountTracker;
@@ -72,8 +72,9 @@
@NonNull
private static double[] buildWorkTypeCdf(
- double pTop, double pFgs, double pEj, double pBg, double pBgUserImp, double pBgUser) {
- return buildCdf(pTop, pFgs, pEj, pBg, pBgUserImp, pBgUser);
+ double pTop, double pFgs, double pUi, double pEj, double pBg,
+ double pBgUserImp, double pBgUser) {
+ return buildCdf(pTop, pFgs, pUi, pEj, pBg, pBgUserImp, pBgUser);
}
@NonNull
@@ -108,23 +109,9 @@
@JobConcurrencyManager.WorkType
static int getRandomWorkType(double[] cdf, double rand) {
+ assertThat(cdf.length).isEqualTo(NUM_WORK_TYPES);
final int index = getRandomIndex(cdf, rand);
- switch (index) {
- case 0:
- return WORK_TYPE_TOP;
- case 1:
- return WORK_TYPE_FGS;
- case 2:
- return WORK_TYPE_EJ;
- case 3:
- return WORK_TYPE_BG;
- case 4:
- return WORK_TYPE_BGUSER_IMPORTANT;
- case 5:
- return WORK_TYPE_BGUSER;
- default:
- throw new IllegalStateException("Unknown work type");
- }
+ return 1 << index;
}
/**
@@ -326,7 +313,7 @@
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_BG, 1f), Pair.create(WORK_TYPE_BGUSER, .5f));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0.5, 0, 0);
+ final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0, 0.5, 0, 0);
final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
final double probStart = 0.5;
@@ -344,7 +331,7 @@
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_BG, 1f), Pair.create(WORK_TYPE_BGUSER, .5f));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 0, 1.0 / 3);
+ final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 0, 1.0 / 3, 0, 1.0 / 3);
final double[] numTypesCdf = buildCdf(.75, .2, .05);
final double probStart = 0.5;
@@ -362,7 +349,7 @@
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_BG, .2f), Pair.create(WORK_TYPE_BGUSER, .1f));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 0, 1.0 / 3);
+ final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 0, 1.0 / 3, 0, 1.0 / 3);
final double[] numTypesCdf = buildCdf(.05, .95);
final double probStart = 0.5;
@@ -382,7 +369,7 @@
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 2.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.8, 0.02, .08);
+ final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0, 0.8, 0.02, .08);
final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
final double probStart = 0.5;
@@ -402,7 +389,7 @@
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0.85, 0.05, 0, 0.1, 0, 0);
+ final double[] cdf = buildWorkTypeCdf(0.8, 0.05, 0.05, 0, 0.1, 0, 0);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -422,7 +409,7 @@
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.1, 0.05, .75);
+ final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0, 0.1, 0.05, .75);
final double[] numTypesCdf = buildCdf(0.5, 0.5);
final double probStart = 0.5;
@@ -443,7 +430,7 @@
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0.05, 0, 0.05);
+ final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0, 0.05, 0, 0.05);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -464,7 +451,7 @@
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.5, 0, 0.5);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0, 0.5, 0, 0.5);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -485,7 +472,7 @@
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.1, 0, 0.9);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0, 0.1, 0, 0.9);
final double[] numTypesCdf = buildCdf(0.9, 0.1);
final double probStart = 0.5;
@@ -506,7 +493,7 @@
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.9, 0, 0.1);
+ final double[] cdf = buildWorkTypeCdf(0, 0, .4, 0, 0.5, 0, 0.1);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -525,7 +512,7 @@
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0, 0, 0);
+ final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.25, 0.25, 0, 0, 0);
final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2);
final double probStart = 0.5;
@@ -566,7 +553,7 @@
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_EJ, 5.0f / 6), Pair.create(WORK_TYPE_BG, 2.0f / 3));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(.1, 0, 0.5, 0.35, 0, 0.05);
+ final double[] cdf = buildWorkTypeCdf(.1, 0, 0.05, 0.45, 0.35, 0, 0.05);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -586,7 +573,7 @@
List.of(Pair.create(WORK_TYPE_EJ, 5.0f / 6), Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 6));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.4, 0.1, 0, 0.4);
+ final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.2, 0.2, 0.1, 0, 0.4);
final double[] numTypesCdf = buildCdf(0.7, 0.3);
final double probStart = 0.5;
@@ -607,7 +594,7 @@
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1.0f / 7),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 7));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.25, 0.05, 0.3, 0.3);
+ final double[] cdf = buildWorkTypeCdf(0.01, 0.02, 0.09, 0.25, 0.05, 0.3, 0.3);
final double[] numTypesCdf = buildCdf(0.7, 0.3);
final double probStart = 0.5;
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index bd5a063..94dfae3 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -21,6 +21,7 @@
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_UI;
import static com.android.server.job.JobConcurrencyManager.workTypeToString;
import static org.junit.Assert.assertEquals;
@@ -47,6 +48,7 @@
private static final String KEY_MAX_TOTAL = "concurrency_max_total_test";
private static final String KEY_MAX_RATIO_TOP = "concurrency_max_ratio_top_test";
private static final String KEY_MAX_RATIO_FGS = "concurrency_max_ratio_fgs_test";
+ private static final String KEY_MAX_RATIO_UI = "concurrency_max_ratio_ui_test";
private static final String KEY_MAX_RATIO_EJ = "concurrency_max_ratio_ej_test";
private static final String KEY_MAX_RATIO_BG = "concurrency_max_ratio_bg_test";
private static final String KEY_MAX_RATIO_BGUSER_IMPORTANT =
@@ -54,6 +56,7 @@
private static final String KEY_MAX_RATIO_BGUSER = "concurrency_max_ratio_bguser_test";
private static final String KEY_MIN_RATIO_TOP = "concurrency_min_ratio_top_test";
private static final String KEY_MIN_RATIO_FGS = "concurrency_min_ratio_fgs_test";
+ private static final String KEY_MIN_RATIO_UI = "concurrency_min_ratio_ui_test";
private static final String KEY_MIN_RATIO_EJ = "concurrency_min_ratio_ej_test";
private static final String KEY_MIN_RATIO_BG = "concurrency_min_ratio_bg_test";
private static final String KEY_MIN_RATIO_BGUSER_IMPORTANT =
@@ -326,31 +329,35 @@
/* max */ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_BG, 16)));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
- .setInt(KEY_MAX_TOTAL, 16)
+ .setInt(KEY_MAX_TOTAL, 32)
.setFloat(KEY_MAX_RATIO_TOP, 1f)
- .setFloat(KEY_MIN_RATIO_TOP, 1.0f / 16)
- .setFloat(KEY_MAX_RATIO_FGS, 15.0f / 16)
- .setFloat(KEY_MIN_RATIO_FGS, 2.0f / 16)
- .setFloat(KEY_MAX_RATIO_EJ, 14.0f / 16)
- .setFloat(KEY_MIN_RATIO_EJ, 3.0f / 16)
- .setFloat(KEY_MAX_RATIO_BG, 13.0f / 16)
- .setFloat(KEY_MIN_RATIO_BG, 3.0f / 16)
- .setFloat(KEY_MAX_RATIO_BGUSER_IMPORTANT, 12.0f / 16)
- .setFloat(KEY_MIN_RATIO_BGUSER_IMPORTANT, 2.0f / 16)
- .setFloat(KEY_MAX_RATIO_BGUSER, 11.0f / 16)
- .setFloat(KEY_MIN_RATIO_BGUSER, 2.0f / 16)
+ .setFloat(KEY_MIN_RATIO_TOP, 1.0f / 32)
+ .setFloat(KEY_MAX_RATIO_FGS, 15.0f / 32)
+ .setFloat(KEY_MIN_RATIO_FGS, 2.0f / 32)
+ .setFloat(KEY_MAX_RATIO_UI, 10.0f / 32)
+ .setFloat(KEY_MIN_RATIO_UI, 4.0f / 32)
+ .setFloat(KEY_MAX_RATIO_EJ, 14.0f / 32)
+ .setFloat(KEY_MIN_RATIO_EJ, 3.0f / 32)
+ .setFloat(KEY_MAX_RATIO_BG, 13.0f / 32)
+ .setFloat(KEY_MIN_RATIO_BG, 3.0f / 32)
+ .setFloat(KEY_MAX_RATIO_BGUSER_IMPORTANT, 12.0f / 32)
+ .setFloat(KEY_MIN_RATIO_BGUSER_IMPORTANT, 2.0f / 32)
+ .setFloat(KEY_MAX_RATIO_BGUSER, 11.0f / 32)
+ .setFloat(KEY_MIN_RATIO_BGUSER, 2.0f / 32)
.build(),
- /* limit */ 16,
+ /* limit */ 32,
/*default*/ 9,
/* min */ List.of(Pair.create(WORK_TYPE_BG, .99f)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 1f)),
- /*expected*/ true, 16,
+ /*expected*/ true, 32,
/* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_FGS, 2),
+ Pair.create(WORK_TYPE_UI, 4),
Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 3),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2),
Pair.create(WORK_TYPE_BGUSER, 2)),
/* max */
- List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_FGS, 15),
+ List.of(Pair.create(WORK_TYPE_TOP, 32), Pair.create(WORK_TYPE_FGS, 15),
+ Pair.create(WORK_TYPE_UI, 10),
Pair.create(WORK_TYPE_EJ, 14), Pair.create(WORK_TYPE_BG, 13),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 12),
Pair.create(WORK_TYPE_BGUSER, 11)));
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index fa8d866..93f6db7 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -500,6 +500,7 @@
// The cached conversations are above the limit because every conversation has active
// notifications. To uncache one of them, the notifications for that conversation need to
// be dismissed.
+ String notificationKey = "";
for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
String shortcutId = TEST_SHORTCUT_ID + i;
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
@@ -507,11 +508,13 @@
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
when(mNotification.getShortcutId()).thenReturn(shortcutId);
- sendGenericNotification();
+ notificationKey = String.format("notification-key-%d", i);
+ sendGenericNotificationWithKey(notificationKey);
}
// Post another notification for the last conversation.
- sendGenericNotification();
+ String otherNotificationKey = "other-notification-key";
+ sendGenericNotificationWithKey(otherNotificationKey);
// Removing one of the two notifications does not un-cache the shortcut.
listenerService.onNotificationRemoved(mGenericSbn, null,
@@ -520,6 +523,7 @@
anyInt(), any(), anyString(), any(), anyInt(), anyInt());
// Removing the second notification un-caches the shortcut.
+ when(mGenericSbn.getKey()).thenReturn(notificationKey);
listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CANCEL_ALL);
verify(mShortcutServiceInternal).uncacheShortcuts(
@@ -687,6 +691,35 @@
}
@Test
+ public void testGetConversation_trackActiveConversations() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+ assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isNull();
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+ assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isNotNull();
+
+ sendGenericNotification();
+ sendGenericNotification();
+ ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID);
+ assertTrue(result.hasActiveNotifications());
+
+ // Both generic notifications have the same notification key, so a single dismiss will
+ // remove both of them.
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationRemoved(mGenericSbn, null,
+ NotificationListenerService.REASON_CANCEL);
+ ConversationChannel resultTwo = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID);
+ assertFalse(resultTwo.hasActiveNotifications());
+ }
+
+ @Test
public void testGetConversation_unsyncedShortcut() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
@@ -1322,7 +1355,7 @@
sendGenericNotification();
- mDataManager.getRecentConversations(USER_ID_PRIMARY);
+ mDataManager.getRecentConversations(USER_ID_PRIMARY);
verify(mShortcutServiceInternal).getShortcuts(
anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
@@ -1693,6 +1726,12 @@
// "Sends" a notification to a non-customized notification channel - the notification channel
// is something generic like "messages" and the notification has a shortcut id
private void sendGenericNotification() {
+ sendGenericNotificationWithKey(GENERIC_KEY);
+ }
+
+ // "Sends" a notification to a non-customized notification channel with the specified key.
+ private void sendGenericNotificationWithKey(String key) {
+ when(mGenericSbn.getKey()).thenReturn(key);
when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID);
doAnswer(invocationOnMock -> {
NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
@@ -1708,7 +1747,7 @@
mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
false, false, false, null, 0, false, 0);
return true;
- }).when(mRankingMap).getRanking(eq(GENERIC_KEY),
+ }).when(mRankingMap).getRanking(eq(key),
any(NotificationListenerService.Ranking.class));
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 26d0ddf..f568c76 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -64,6 +64,8 @@
.setUseParentsContacts(false)
.setCrossProfileIntentFilterAccessControl(10)
.setCrossProfileIntentResolutionStrategy(0)
+ .setIsMediaSharedWithParent(false)
+ .setIsCredentialSharableWithParent(true)
.build();
final UserProperties actualProps = new UserProperties(defaultProps);
actualProps.setShowInLauncher(14);
@@ -72,6 +74,8 @@
actualProps.setUseParentsContacts(true);
actualProps.setCrossProfileIntentFilterAccessControl(20);
actualProps.setCrossProfileIntentResolutionStrategy(1);
+ actualProps.setIsMediaSharedWithParent(true);
+ actualProps.setIsCredentialSharableWithParent(false);
// Write the properties to xml.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -111,6 +115,7 @@
.setStartWithParent(true)
.setShowInSettings(3452)
.setInheritDevicePolicy(1732)
+ .setIsMediaSharedWithParent(true)
.build();
final UserProperties orig = new UserProperties(defaultProps);
orig.setShowInLauncher(2841);
@@ -169,7 +174,10 @@
// Items with no permission requirements.
assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
-
+ assertEqualGetterOrThrows(orig::getIsMediaSharedWithParent,
+ copy::getIsMediaSharedWithParent, true);
+ assertEqualGetterOrThrows(orig::getIsCredentialSharableWithParent,
+ copy::getIsCredentialSharableWithParent, true);
}
/**
@@ -215,5 +223,9 @@
.isEqualTo(actual.getCrossProfileIntentFilterAccessControl());
assertThat(expected.getCrossProfileIntentResolutionStrategy())
.isEqualTo(actual.getCrossProfileIntentResolutionStrategy());
+ assertThat(expected.getIsMediaSharedWithParent())
+ .isEqualTo(actual.getIsMediaSharedWithParent());
+ assertThat(expected.getIsCredentialSharableWithParent())
+ .isEqualTo(actual.getIsCredentialSharableWithParent());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 928c6ef..a2bbfba 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -86,7 +86,9 @@
.setShowInLauncher(17)
.setUseParentsContacts(true)
.setCrossProfileIntentFilterAccessControl(10)
- .setCrossProfileIntentResolutionStrategy(1);
+ .setCrossProfileIntentResolutionStrategy(1)
+ .setIsMediaSharedWithParent(true)
+ .setIsCredentialSharableWithParent(false);
final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
.setEnabled(1)
@@ -148,6 +150,8 @@
.getCrossProfileIntentFilterAccessControl());
assertEquals(1, type.getDefaultUserPropertiesReference()
.getCrossProfileIntentResolutionStrategy());
+ assertTrue(type.getDefaultUserPropertiesReference().getIsMediaSharedWithParent());
+ assertFalse(type.getDefaultUserPropertiesReference().getIsCredentialSharableWithParent());
assertEquals(23, type.getBadgeLabel(0));
assertEquals(24, type.getBadgeLabel(1));
@@ -196,6 +200,8 @@
assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
assertEquals(UserProperties.CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT,
props.getCrossProfileIntentResolutionStrategy());
+ assertFalse(props.getIsMediaSharedWithParent());
+ assertFalse(props.getIsCredentialSharableWithParent());
assertFalse(type.hasBadge());
}
@@ -279,7 +285,9 @@
.setStartWithParent(true)
.setUseParentsContacts(true)
.setCrossProfileIntentFilterAccessControl(10)
- .setCrossProfileIntentResolutionStrategy(1);
+ .setCrossProfileIntentResolutionStrategy(1)
+ .setIsMediaSharedWithParent(false)
+ .setIsCredentialSharableWithParent(true);
final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
builders.put(userTypeAosp1, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
@@ -312,6 +320,9 @@
assertTrue(aospType.getDefaultUserPropertiesReference().getStartWithParent());
assertTrue(aospType.getDefaultUserPropertiesReference()
.getUseParentsContacts());
+ assertFalse(aospType.getDefaultUserPropertiesReference().getIsMediaSharedWithParent());
+ assertTrue(aospType.getDefaultUserPropertiesReference()
+ .getIsCredentialSharableWithParent());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -348,6 +359,9 @@
assertFalse(aospType.getDefaultUserPropertiesReference().getStartWithParent());
assertFalse(aospType.getDefaultUserPropertiesReference()
.getUseParentsContacts());
+ assertTrue(aospType.getDefaultUserPropertiesReference().getIsMediaSharedWithParent());
+ assertFalse(aospType.getDefaultUserPropertiesReference()
+ .getIsCredentialSharableWithParent());
// userTypeOem1 should be created.
UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 1305e07..f2bc654 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
@@ -189,6 +188,10 @@
cloneUserProperties::getCrossProfileIntentFilterAccessControl);
assertThrows(SecurityException.class,
cloneUserProperties::getCrossProfileIntentResolutionStrategy);
+ assertThat(typeProps.getIsMediaSharedWithParent())
+ .isEqualTo(cloneUserProperties.getIsMediaSharedWithParent());
+ assertThat(typeProps.getIsCredentialSharableWithParent())
+ .isEqualTo(cloneUserProperties.getIsCredentialSharableWithParent());
// Verify clone user parent
assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
@@ -834,11 +837,13 @@
// provided that the test caller has the necessary permissions.
assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
- assertFalse(userProps.getUseParentsContacts());
+ assertThat(userProps.getUseParentsContacts()).isFalse();
assertThrows(SecurityException.class, userProps::getCrossProfileIntentFilterAccessControl);
assertThrows(SecurityException.class, userProps::getCrossProfileIntentResolutionStrategy);
assertThrows(SecurityException.class, userProps::getStartWithParent);
assertThrows(SecurityException.class, userProps::getInheritDevicePolicy);
+ assertThat(userProps.getIsMediaSharedWithParent()).isFalse();
+ assertThat(userProps.getIsCredentialSharableWithParent()).isTrue();
}
// Make sure only max managed profiles can be created
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 261156611..7fac9b6 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -35,6 +35,7 @@
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
+import android.os.PowerManager;
import androidx.annotation.NonNull;
@@ -312,6 +313,14 @@
+ " </sensor>\n"
+ " </conditions>\n"
+ " </device-state>\n"
+ + " <device-state>\n"
+ + " <identifier>4</identifier>\n"
+ + " <name>THERMAL_TEST</name>\n"
+ + " <flags>\n"
+ + " <flag>FLAG_EMULATED_ONLY</flag>\n"
+ + " <flag>FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
+ + " </flags>\n"
+ + " </device-state>\n"
+ "</device-state-config>\n";
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
return DeviceStateProviderImpl.createFromConfig(mContext,
@@ -332,7 +341,10 @@
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */) },
+ new DeviceState(3, "OPENED", 0 /* flags */),
+ new DeviceState(4, "THERMAL_TEST",
+ DeviceState.FLAG_EMULATED_ONLY
+ | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
// the initial sensor state.
@@ -376,6 +388,57 @@
}
@Test
+ public void test_flagDisableWhenThermalStatusCritical() throws Exception {
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+ provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */),
+ new DeviceState(4, "THERMAL_TEST",
+ DeviceState.FLAG_EMULATED_ONLY
+ | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+ mDeviceStateArrayCaptor.getValue());
+ Mockito.clearInvocations(listener);
+
+ provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ Mockito.clearInvocations(listener);
+
+ // The THERMAL_TEST state should be disabled.
+ provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */) },
+ mDeviceStateArrayCaptor.getValue());
+ Mockito.clearInvocations(listener);
+
+ // The THERMAL_TEST state should be re-enabled.
+ provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */),
+ new DeviceState(4, "THERMAL_TEST",
+ DeviceState.FLAG_EMULATED_ONLY
+ | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+ mDeviceStateArrayCaptor.getValue());
+ }
+
+ @Test
public void test_invalidSensorValues() throws Exception {
// onStateChanged() should not be triggered by invalid sensor values.
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index c2556e9..34e45c2 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -25,6 +25,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.os.Handler;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -35,6 +36,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.util.HashSet;
import java.util.Set;
@@ -55,12 +57,13 @@
private static final int TEST_UID_5 = 76421423;
private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private final Handler mHandler = Mockito.mock(Handler.class);
private final ThreadLocalRandom mRandom = ThreadLocalRandom.current();
@Test
public void removesOldWakeups() {
// The xml resource doesn't matter for this test.
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler);
final Set<Long> timestamps = new HashSet<>();
final long firstWakeup = 453192;
@@ -88,7 +91,7 @@
@Test
public void alarmIrqAttributionSolo() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 12423121;
obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_ALARM_IRQ);
@@ -113,7 +116,7 @@
@Test
public void alarmIrqAttributionCombined() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 92123210;
obj.noteWakeupTimeAndReason(wakeupTime, 4,
@@ -143,7 +146,7 @@
@Test
public void unknownIrqAttribution() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 92123410;
obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ);
@@ -163,7 +166,7 @@
@Test
public void unknownAttribution() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 72123210;
obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
@@ -178,7 +181,7 @@
@Test
public void unsupportedAttribution() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
long wakeupTime = 970934;
obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED);
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
new file mode 100644
index 0000000..0be678a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.server.rollback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.VersionedPackage;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.SystemConfig;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Scanner;
+
+@RunWith(AndroidJUnit4.class)
+public class RollbackPackageHealthObserverTest {
+ private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
+
+ private SystemConfig mSysConfig;
+
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Before
+ public void setup() {
+ mSysConfig = new SystemConfigTestClass();
+ }
+
+ /**
+ * Subclass of SystemConfig without running the constructor.
+ */
+ private class SystemConfigTestClass extends SystemConfig {
+ SystemConfigTestClass() {
+ super(false);
+ }
+ }
+
+ /**
+ * Test that isAutomaticRollbackDenied works correctly when packages that are not
+ * denied are sent.
+ */
+ @Test
+ public void isRollbackAllowedTest_false() throws IOException {
+ final String contents =
+ "<config>\n"
+ + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+ + "</config>";
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+ new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+ }
+
+ /**
+ * Test that isAutomaticRollbackDenied works correctly when packages that are
+ * denied are sent.
+ */
+ @Test
+ public void isRollbackAllowedTest_true() throws IOException {
+ final String contents =
+ "<config>\n"
+ + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+ + "</config>";
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+ new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+ }
+
+ /**
+ * Test that isAutomaticRollbackDenied works correctly when no config is present
+ */
+ @Test
+ public void isRollbackAllowedTest_noConfig() throws IOException {
+ final File folder = createTempSubfolder("folder");
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
+ new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+ }
+
+ /**
+ * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+ *
+ * @param folder pre-existing subdirectory of mTemporaryFolder to put the file
+ * @param fileName name of the file (e.g. filename.xml) to create
+ * @param contents contents to write to the file
+ * @return the newly created file
+ */
+ private File createTempFile(File folder, String fileName, String contents)
+ throws IOException {
+ File file = new File(folder, fileName);
+ BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+ bw.write(contents);
+ bw.close();
+
+ // Print to logcat for test debugging.
+ Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
+ Scanner input = new Scanner(file);
+ while (input.hasNextLine()) {
+ Log.d(LOG_TAG, input.nextLine());
+ }
+
+ return file;
+ }
+
+ private void readPermissions(File libraryDir, int permissionFlag) {
+ final XmlPullParser parser = Xml.newPullParser();
+ mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
+ }
+
+ /**
+ * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+ *
+ * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
+ * @return the folder
+ */
+ private File createTempSubfolder(String folderName)
+ throws IOException {
+ File folder = new File(mTemporaryFolder.getRoot(), folderName);
+ folder.mkdirs();
+ return folder;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index d073f5b..aca96ad 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -595,6 +595,56 @@
}
/**
+ * Test that getRollbackDenylistedPackages works correctly for the tag:
+ * {@code automatic-rollback-denylisted-app}.
+ */
+ @Test
+ public void automaticRollbackDeny_vending() throws IOException {
+ final String contents =
+ "<config>\n"
+ + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
+ + "</config>";
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages())
+ .containsExactly("com.android.vending");
+ }
+
+ /**
+ * Test that getRollbackDenylistedPackages works correctly for the tag:
+ * {@code automatic-rollback-denylisted-app} without any packages.
+ */
+ @Test
+ public void automaticRollbackDeny_empty() throws IOException {
+ final String contents =
+ "<config>\n"
+ + " <automatic-rollback-denylisted-app />\n"
+ + "</config>";
+ final File folder = createTempSubfolder("folder");
+ createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
+ }
+
+ /**
+ * Test that getRollbackDenylistedPackages works correctly for the tag:
+ * {@code automatic-rollback-denylisted-app} without the corresponding config.
+ */
+ @Test
+ public void automaticRollbackDeny_noConfig() throws IOException {
+ final File folder = createTempSubfolder("folder");
+
+ readPermissions(folder, /* Grant all permission flags */ ~0);
+
+ assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
+ }
+
+ /**
* Tests that readPermissions works correctly for the tag: {@code update-ownership}.
*/
@Test
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 fd1ca68..a76b82b 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -407,7 +407,7 @@
void assertShowRecentApps() {
waitForIdle();
- verify(mStatusBarManagerInternal).showRecentApps(anyBoolean(), anyBoolean());
+ verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
}
void assertSwitchKeyboardLayout() {
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 352e498..3b7b5eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1931,6 +1931,132 @@
}
@Test
+ public void testDisplayAspectRatioForResizablePortraitApps() {
+ // Set up a display in portrait and ignoring orientation request.
+ int displayWidth = 1400;
+ int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(2f);
+
+ // Enable display aspect ratio to take precedence before
+ // fixedOrientationLetterboxAspectRatio
+ mWm.mLetterboxConfiguration
+ .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+
+ // Set up resizable app in portrait
+ prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, displayWidth, getExpectedSplitSize(displayHeight));
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+ // App should launch in fixed orientation letterbox.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ // Checking that there is no size compat mode.
+ assertFitted();
+ // Check that the display aspect ratio is used by the app.
+ final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
+ final float delta = 0.01f;
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(mActivity.getBounds()), delta);
+ }
+
+ @Test
+ public void testDisplayAspectRatioForResizableLandscapeApps() {
+ // Set up a display in landscape and ignoring orientation request.
+ int displayWidth = 1600;
+ int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(2f);
+
+ // Enable display aspect ratio to take precedence before
+ // fixedOrientationLetterboxAspectRatio
+ mWm.mLetterboxConfiguration
+ .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+
+ // Set up resizable app in landscape
+ prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_LANDSCAPE, false /* isUnresizable */);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(displayWidth), displayHeight);
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+ // App should launch in fixed orientation letterbox.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ // Checking that there is no size compat mode.
+ assertFitted();
+ // Check that the display aspect ratio is used by the app.
+ final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
+ final float delta = 0.01f;
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(mActivity.getBounds()), delta);
+ }
+
+ @Test
+ public void testDisplayAspectRatioForUnresizableLandscapeApps() {
+ // Set up a display in portrait and ignoring orientation request.
+ int displayWidth = 1400;
+ int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
+ // Enable display aspect ratio to take precedence before
+ // fixedOrientationLetterboxAspectRatio
+ mWm.mLetterboxConfiguration
+ .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ // App should launch in fixed orientation letterbox.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ // Checking that there is no size compat mode.
+ assertFitted();
+ // Check that the display aspect ratio is used by the app.
+ final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
+ final float delta = 0.01f;
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(mActivity.getBounds()), delta);
+ }
+
+ @Test
+ public void testDisplayAspectRatioForUnresizablePortraitApps() {
+ // Set up a display in landscape and ignoring orientation request.
+ int displayWidth = 1600;
+ int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
+ // Enable display aspect ratio to take precedence before
+ // fixedOrientationLetterboxAspectRatio
+ mWm.mLetterboxConfiguration
+ .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ // App should launch in fixed orientation letterbox.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ // Checking that there is no size compat mode.
+ assertFitted();
+ // Check that the display aspect ratio is used by the app.
+ final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
+ final float delta = 0.01f;
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(mActivity.getBounds()), delta);
+ }
+
+ @Test
public void
testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() {
// Set up a display in landscape and ignoring orientation request.
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 0231b46..9018138 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -564,20 +564,29 @@
final WindowProcessController delegateProc = mSystemServicesTestRule.addProcess(
"pkg.delegate", "proc.delegate", 6000 /* pid */, 6000 /* uid */);
doReturn(mock(IBinder.class)).when(delegateProc.getThread()).asBinder();
- final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false).build();
+ final Task task = app.getTask();
+ task.setTaskOrganizer(mock(ITaskOrganizer.class), true /* skipTaskAppeared */);
+ app.setVisibleRequested(true);
final TransitionController controller = app.mTransitionController;
final Transition transition = controller.createTransition(TRANSIT_OPEN);
final RemoteTransition remoteTransition = new RemoteTransition(
mock(IRemoteTransition.class));
remoteTransition.setAppThread(delegateProc.getThread());
- transition.collectExistenceChange(app.getTask());
- controller.requestStartTransition(transition, app.getTask(), remoteTransition,
+ transition.collect(app);
+ controller.requestStartTransition(transition, null /* startTask */, remoteTransition,
null /* displayChange */);
testPlayer.startTransition();
+ app.onStartingWindowDrawn();
+ // The task appeared event should be deferred until transition ready.
+ assertFalse(task.taskAppearedReady());
testPlayer.onTransactionReady(app.getSyncTransaction());
+ assertTrue(task.taskAppearedReady());
assertTrue(playerProc.isRunningRemoteTransition());
assertTrue(delegateProc.isRunningRemoteTransition());
assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+ assertTrue(app.isVisible());
testPlayer.finish();
assertFalse(playerProc.isRunningRemoteTransition());
@@ -1114,6 +1123,14 @@
assertFalse(activity1.isVisible());
assertTrue(activity2.isVisible());
+
+ // The abort should still commit visible-requested to visible.
+ final Transition abortTransition = controller.createTransition(TRANSIT_OPEN);
+ abortTransition.collect(activity1);
+ activity1.setVisibleRequested(true);
+ activity1.setVisible(false);
+ abortTransition.abort();
+ assertTrue(activity1.isVisible());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index bbdf621..e5efe05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -262,6 +262,12 @@
// device form factors.
mAtm.mWindowManager.mLetterboxConfiguration
.setIsSplitScreenAspectRatioForUnresizableAppsEnabled(false);
+ // Ensure aspect ratio for al apps isn't overridden on any device target.
+ // {@link com.android.internal.R.bool
+ // .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled}, may be set on
+ // some device form factors.
+ mAtm.mWindowManager.mLetterboxConfiguration
+ .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false);
checkDeviceSpecificOverridesNotApplied();
}
@@ -276,6 +282,8 @@
mAtm.mWindowManager.mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
mAtm.mWindowManager.mLetterboxConfiguration
.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+ mAtm.mWindowManager.mLetterboxConfiguration
+ .resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
}
/**
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 04c8f8f..127195c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -21,7 +21,7 @@
import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.service.voice.VoiceInteractionService.KEY_SHOW_SESSION_ID;
+import static android.service.voice.VoiceInteractionSession.KEY_SHOW_SESSION_ID;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING
index 0dcf1b6..775f1b8 100644
--- a/telecomm/TEST_MAPPING
+++ b/telecomm/TEST_MAPPING
@@ -25,14 +25,6 @@
]
},
{
- "name": "CtsTelephonyTestCases",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- },
- {
"name": "CtsTelephony2TestCases",
"options": [
{
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 770a374..315ac67 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -21,6 +21,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.Bundle;
import android.os.OutcomeReceiver;
@@ -45,7 +46,8 @@
* {@link OutcomeReceiver#onError} is called and provides a {@link CallException} that details why
* the operation failed.
*/
-public final class CallControl implements AutoCloseable {
+@SuppressLint("NotCloseable")
+public final class CallControl {
private static final String TAG = CallControl.class.getSimpleName();
private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
private final String mCallId;
@@ -261,17 +263,6 @@
}
/**
- * This method should be called after
- * {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
- * {@link CallControl#rejectCall(Executor, OutcomeReceiver)}
- * to destroy all references of this object and avoid memory leaks.
- */
- @Override
- public void close() {
- mRepository.removeCallFromServiceWrapper(mPhoneAccountHandle, mCallId);
- }
-
- /**
* Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
* wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
* response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 95a8e16..fd2907c 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -32,8 +32,10 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/**
* A unified virtual device providing a means of voice (and other) communication on a device.
@@ -148,6 +150,14 @@
private final Object mLock = new Object();
+ // Future used to delay terminating the InCallService before the call disconnect tone
+ // finishes playing.
+ private static Map<String, CompletableFuture<Void>> sDisconnectedToneFutures = new ArrayMap<>();
+
+ // Timeout value to be used to ensure future completion for sDisconnectedToneFutures. This is
+ // set to 4 seconds to account for the exceptional case (TONE_CONGESTION).
+ private static final int DISCONNECTED_TONE_TIMEOUT = 4000;
+
Phone(InCallAdapter adapter, String callingPackage, int targetSdkVersion) {
mInCallAdapter = adapter;
mCallingPackage = callingPackage;
@@ -456,9 +466,45 @@
}
private void fireCallRemoved(Call call) {
- for (Listener listener : mListeners) {
- listener.onCallRemoved(this, call);
+ String callId = call.internalGetCallId();
+ CompletableFuture<Void> disconnectedToneFuture = initializeDisconnectedToneFuture(callId);
+ // delay the InCallService termination until after the disconnect tone finishes playing
+ disconnectedToneFuture.thenRunAsync(() -> {
+ for (Listener listener : mListeners) {
+ listener.onCallRemoved(this, call);
+ }
+ // clean up the future after
+ sDisconnectedToneFutures.remove(callId);
+ });
+ }
+
+ /**
+ * Initialize disconnect tone future to be used in delaying ICS termination.
+ *
+ * @return CompletableFuture to delay InCallService termination until after the disconnect tone
+ * finishes playing. A timeout of 4s is used to handle the use case when we play
+ * TONE_CONGESTION and to ensure completion so that we don't block the removal of the service.
+ */
+ private CompletableFuture<Void> initializeDisconnectedToneFuture(String callId) {
+ // create the future and map (sDisconnectedToneFutures) it to the corresponding call id
+ CompletableFuture<Void> disconnectedToneFuture = new CompletableFuture<Void>()
+ .completeOnTimeout(null, DISCONNECTED_TONE_TIMEOUT, TimeUnit.MILLISECONDS);
+ // we should not encounter duplicate insertions since call ids are unique
+ sDisconnectedToneFutures.put(callId, disconnectedToneFuture);
+ return disconnectedToneFuture;
+ }
+
+ /**
+ * Completes disconnected tone future with passed in result.
+ * @hide
+ * @return true if future was completed, false otherwise
+ */
+ public static boolean completeDisconnectedToneFuture(String callId) {
+ if (sDisconnectedToneFutures.containsKey(callId)) {
+ sDisconnectedToneFutures.get(callId).complete(null);
+ return true;
}
+ return false;
}
private void fireCallAudioStateChanged(CallAudioState audioState) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b55a29c..7950351 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7510,6 +7510,48 @@
public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL =
KEY_PREFIX + "prefer_ims_emergency_when_voice_calls_on_cs_bool";
+ /** @hide */
+ @IntDef({
+ VOWIFI_REQUIRES_NONE,
+ VOWIFI_REQUIRES_SETTING_ENABLED,
+ VOWIFI_REQUIRES_VALID_EID,
+ })
+ public @interface VoWiFiRequires {}
+
+ /**
+ * Default value.
+ * If {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true},
+ * VoWi-Fi emergency call shall be attempted if Wi-Fi network is connected.
+ * Otherwise, it shall be attempted if IMS is registered over Wi-Fi.
+ * @hide
+ */
+ public static final int VOWIFI_REQUIRES_NONE = 0;
+
+ /**
+ * VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
+ * and Wi-Fi calling setting is enabled. This value is applicable if the value of
+ * {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
+ * @hide
+ */
+ public static final int VOWIFI_REQUIRES_SETTING_ENABLED = 1;
+
+ /**
+ * VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
+ * and Wi-Fi calling is activated successfully. This value is applicable if the value of
+ * {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
+ * @hide
+ */
+ public static final int VOWIFI_REQUIRES_VALID_EID = 2;
+
+ /**
+ * Specifies the condition when emergency call shall be attempted on IMS over Wi-Fi.
+ *
+ * The default value for this key is {@code #VOWIFI_REQUIRES_NONE}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT =
+ KEY_PREFIX + "emergency_vowifi_requires_condition_int";
+
/**
* Specifies maximum number of emergency call retries over Wi-Fi.
* This is valid only when {@link #DOMAIN_PS_NON_3GPP} is included in
@@ -7621,7 +7663,8 @@
KEY_PREFIX + "emergency_cdma_preferred_numbers_string_array";
/**
- * Specifies if emergency call shall be attempted on IMS only when VoLTE is enabled.
+ * Specifies if emergency call shall be attempted on IMS over cellular network
+ * only when VoLTE is enabled.
*
* The default value for this key is {@code false}.
* @hide
@@ -7685,6 +7728,7 @@
});
defaults.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, false);
+ defaults.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, VOWIFI_REQUIRES_NONE);
defaults.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 1);
defaults.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, 10);
defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index ceea94b..eac4d16 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -104,12 +104,15 @@
private int mCarrierRestrictionDefault;
@MultiSimPolicy
private int mMultiSimPolicy;
+ @TelephonyManager.CarrierRestrictionStatus
+ private int mCarrierRestrictionStatus;
private CarrierRestrictionRules() {
mAllowedCarriers = new ArrayList<CarrierIdentifier>();
mExcludedCarriers = new ArrayList<CarrierIdentifier>();
mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
mMultiSimPolicy = MULTISIM_POLICY_NONE;
+ mCarrierRestrictionStatus = TelephonyManager.CARRIER_RESTRICTION_STATUS_UNKNOWN;
}
private CarrierRestrictionRules(Parcel in) {
@@ -120,6 +123,7 @@
in.readTypedList(mExcludedCarriers, CarrierIdentifier.CREATOR);
mCarrierRestrictionDefault = in.readInt();
mMultiSimPolicy = in.readInt();
+ mCarrierRestrictionStatus = in.readInt();
}
/**
@@ -289,6 +293,11 @@
return true;
}
+ /** @hide */
+ public int getCarrierRestrictionStatus() {
+ return mCarrierRestrictionStatus;
+ }
+
/**
* {@link Parcelable#writeToParcel}
*/
@@ -298,6 +307,7 @@
out.writeTypedList(mExcludedCarriers);
out.writeInt(mCarrierRestrictionDefault);
out.writeInt(mMultiSimPolicy);
+ out.writeInt(mCarrierRestrictionStatus);
}
/**
@@ -399,5 +409,17 @@
mRules.mMultiSimPolicy = multiSimPolicy;
return this;
}
+
+ /**
+ * Set the device's carrier restriction status
+ *
+ * @param carrierRestrictionStatus device restriction status
+ * @hide
+ */
+ public @NonNull
+ Builder setCarrierRestrictionStatus(int carrierRestrictionStatus) {
+ mRules.mCarrierRestrictionStatus = carrierRestrictionStatus;
+ return this;
+ }
}
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 26b4bbc..40488b1 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2547,8 +2547,6 @@
/**
* User is not associated with the subscription.
- * TODO(b/263279115): Make this error code public.
- * @hide
*/
public static final int RESULT_USER_NOT_ALLOWED = 33;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 0638189..189a08c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1139,6 +1139,14 @@
*/
public static final String USER_HANDLE = SimInfo.COLUMN_USER_HANDLE;
+ /**
+ * TelephonyProvider column name for satellite enabled.
+ * By default, it's disabled.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String SATELLITE_ENABLED = SimInfo.COLUMN_SATELLITE_ENABLED;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
@@ -2672,10 +2680,10 @@
* @param columnName Column name in subscription database.
*
* @return Value in string format associated with {@code subscriptionId} and {@code columnName}
- * from the database.
+ * from the database. Empty string if the {@code subscriptionId} is invalid (for backward
+ * compatible).
*
- * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
- * exposed.
+ * @throws IllegalArgumentException if the field is not exposed.
*
* @see android.provider.Telephony.SimInfo for all the columns.
*
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2a965ba..37d1e94 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13162,6 +13162,81 @@
}
/**
+ * Carrier restriction status value is unknown, in case modem did not provide any
+ * information about carrier restriction status.
+ */
+ public static final int CARRIER_RESTRICTION_STATUS_UNKNOWN = 0;
+
+ /** The device is not restricted to a carrier */
+ public static final int CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED = 1;
+
+ /** The device is restricted to a carrier. */
+ public static final int CARRIER_RESTRICTION_STATUS_RESTRICTED = 2;
+
+ /** The device is restricted to the carrier of the calling application. */
+ public static final int CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER = 3;
+
+ /** @hide */
+ @IntDef(prefix = {"CARRIER_RESTRICTION_STATUS_"}, value = {
+ CARRIER_RESTRICTION_STATUS_UNKNOWN,
+ CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED,
+ CARRIER_RESTRICTION_STATUS_RESTRICTED,
+ CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER
+ })
+
+ public @interface CarrierRestrictionStatus {
+ }
+
+ /**
+ * Get the carrier restriction status of the device.
+ * <p>To fetch the carrier restriction status of the device the calling application needs to be
+ * allowlisted to Android at <a href="https://android.googlesource.com/platform/packages/services/Telephony/+/master/assets/CarrierRestrictionOperatorDetails.json">here</a>.
+ * The calling application also needs the READ_PHONE_STATE permission.
+ * The return value of the API is as follows.
+ * <ul>
+ * <li>return {@link #CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER} if the caller
+ * and the device locked by the network are same</li>
+ * <li>return {@link #CARRIER_RESTRICTION_STATUS_RESTRICTED} if the caller and the
+ * device locked by the network are different</li>
+ * <li>return {@link #CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED} if the device is
+ * not locked</li>
+ * <li>return {@link #CARRIER_RESTRICTION_STATUS_UNKNOWN} if the device locking
+ * state is unavailable or radio does not supports the feature</li>
+ * </ul>
+ *
+ * @param executor The executor on which the result listener will be called.
+ * @param resultListener {@link Consumer} that will be called with the result fetched
+ * from the radio of type {@link CarrierRestrictionStatus}
+ * @throws SecurityException if the caller does not have the required permission/privileges or
+ * if the caller is not pre-registered.
+ */
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void getCarrierRestrictionStatus(@NonNull Executor executor,
+ @NonNull @CarrierRestrictionStatus
+ Consumer<Integer> resultListener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(resultListener);
+
+ IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(@CarrierRestrictionStatus int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(result)));
+ }
+ };
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ service.getCarrierRestrictionStatus(internalCallback, getOpPackageName());
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getCarrierRestrictionStatus: RemoteException = " + ex);
+ throw ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Used to enable or disable carrier data by the system based on carrier signalling or
* carrier privileged apps. Different from {@link #setDataEnabled(boolean)} which is linked to
* user settings, carrier data on/off won't affect user settings but will bypass the
@@ -15002,6 +15077,14 @@
@TestApi
public static final int HAL_SERVICE_IMS = 7;
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioSatellite
+ * {@link RadioSatelliteProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_SATELLITE = 8;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"HAL_SERVICE_"},
@@ -15014,6 +15097,7 @@
HAL_SERVICE_SIM,
HAL_SERVICE_VOICE,
HAL_SERVICE_IMS,
+ HAL_SERVICE_SATELLITE
})
public @interface HalService {}
@@ -18071,8 +18155,7 @@
case "carrier": return ALLOWED_NETWORK_TYPES_REASON_CARRIER;
case "enable_2g": return ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G;
case "user_restrictions": return ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS;
- default: throw new IllegalArgumentException("allowedNetworkTypesReasonFromString: "
- + "invalid reason " + reason);
+ default: return -1;
}
}
}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
new file mode 100644
index 0000000..d6dd57a
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -0,0 +1,144 @@
+/*
+ * 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 android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class PointingInfo implements Parcelable {
+ /** Satellite azimuth in degrees */
+ private float mSatelliteAzimuthDegrees;
+
+ /** Satellite elevation in degrees */
+ private float mSatelliteElevationDegrees;
+
+ /** Antenna azimuth in degrees */
+ private float mAntennaAzimuthDegrees;
+
+ /**
+ * Angle of rotation about the x axis. This value represents the angle between a plane
+ * parallel to the device's screen and a plane parallel to the ground.
+ */
+ private float mAntennaPitchDegrees;
+
+ /**
+ * Angle of rotation about the y axis. This value represents the angle between a plane
+ * perpendicular to the device's screen and a plane parallel to the ground.
+ */
+ private float mAntennaRollDegrees;
+
+ /**
+ * @hide
+ */
+ public PointingInfo(float satelliteAzimuthDegress, float satelliteElevationDegress,
+ float antennaAzimuthDegrees, float antennaPitchDegrees, float antennaRollDegrees) {
+ mSatelliteAzimuthDegrees = satelliteAzimuthDegress;
+ mSatelliteElevationDegrees = satelliteElevationDegress;
+ mAntennaAzimuthDegrees = antennaAzimuthDegrees;
+ mAntennaPitchDegrees = antennaPitchDegrees;
+ mAntennaRollDegrees = antennaRollDegrees;
+ }
+
+ private PointingInfo(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeFloat(mSatelliteAzimuthDegrees);
+ out.writeFloat(mSatelliteElevationDegrees);
+ out.writeFloat(mAntennaAzimuthDegrees);
+ out.writeFloat(mAntennaPitchDegrees);
+ out.writeFloat(mAntennaRollDegrees);
+ }
+
+ public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
+ new Creator<PointingInfo>() {
+ @Override
+ public PointingInfo createFromParcel(Parcel in) {
+ return new PointingInfo(in);
+ }
+
+ @Override
+ public PointingInfo[] newArray(int size) {
+ return new PointingInfo[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SatelliteAzimuthDegrees:");
+ sb.append(mSatelliteAzimuthDegrees);
+ sb.append(",");
+
+ sb.append("SatelliteElevationDegrees:");
+ sb.append(mSatelliteElevationDegrees);
+ sb.append(",");
+
+ sb.append("AntennaAzimuthDegrees:");
+ sb.append(mAntennaAzimuthDegrees);
+ sb.append(",");
+
+ sb.append("AntennaPitchDegrees:");
+ sb.append(mAntennaPitchDegrees);
+ sb.append(",");
+
+ sb.append("AntennaRollDegrees:");
+ sb.append(mAntennaRollDegrees);
+ return sb.toString();
+ }
+
+ public float getSatelliteAzimuthDegrees() {
+ return mSatelliteAzimuthDegrees;
+ }
+
+ public float getSatelliteElevationDegrees() {
+ return mSatelliteElevationDegrees;
+ }
+
+ public float getAntennaAzimuthDegrees() {
+ return mAntennaAzimuthDegrees;
+ }
+
+ public float getAntennaPitchDegrees() {
+ return mAntennaPitchDegrees;
+ }
+
+ public float getAntennaRollDegrees() {
+ return mAntennaRollDegrees;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mSatelliteAzimuthDegrees = in.readFloat();
+ mSatelliteElevationDegrees = in.readFloat();
+ mAntennaAzimuthDegrees = in.readFloat();
+ mAntennaPitchDegrees = in.readFloat();
+ mAntennaRollDegrees = in.readFloat();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
new file mode 100644
index 0000000..c5ae4db
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -0,0 +1,201 @@
+/*
+ * 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 android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public final class SatelliteCapabilities implements Parcelable {
+ /**
+ * List of technologies supported by the satellite modem.
+ */
+ private Set<Integer> mSupportedRadioTechnologies;
+
+ /**
+ * Whether satellite mode is always on (this to indicate power impact of keeping it on is
+ * very minimal).
+ */
+ private boolean mIsAlwaysOn;
+
+ /**
+ * Whether UE needs to point to a satellite to send and receive data.
+ */
+ private boolean mNeedsPointingToSatellite;
+
+ /**
+ * List of features supported by the Satellite modem.
+ */
+ private Set<Integer> mSupportedFeatures;
+
+ /**
+ * Whether UE needs a separate SIM profile to communicate with the Satellite network.
+ */
+ private boolean mNeedsSeparateSimProfile;
+
+ /**
+ * @hide
+ */
+ public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
+ boolean needsPointingToSatellite, Set<Integer> supportedFeatures,
+ boolean needsSeparateSimProfile) {
+ mSupportedRadioTechnologies = supportedRadioTechnologies;
+ mIsAlwaysOn = isAlwaysOn;
+ mNeedsPointingToSatellite = needsPointingToSatellite;
+ mSupportedFeatures = supportedFeatures;
+ mNeedsSeparateSimProfile = needsSeparateSimProfile;
+ }
+
+ private SatelliteCapabilities(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) {
+ out.writeInt(mSupportedRadioTechnologies.size());
+ for (int technology : mSupportedRadioTechnologies) {
+ out.writeInt(technology);
+ }
+ } else {
+ out.writeInt(0);
+ }
+
+ out.writeBoolean(mIsAlwaysOn);
+ out.writeBoolean(mNeedsPointingToSatellite);
+
+ if (mSupportedFeatures != null && !mSupportedFeatures.isEmpty()) {
+ out.writeInt(mSupportedFeatures.size());
+ for (int feature : mSupportedFeatures) {
+ out.writeInt(feature);
+ }
+ } else {
+ out.writeInt(0);
+ }
+
+ out.writeBoolean(mNeedsSeparateSimProfile);
+ }
+
+ public static final @android.annotation.NonNull Creator<SatelliteCapabilities> CREATOR =
+ new Creator<SatelliteCapabilities>() {
+ @Override
+ public SatelliteCapabilities createFromParcel(Parcel in) {
+ return new SatelliteCapabilities(in);
+ }
+
+ @Override
+ public SatelliteCapabilities[] newArray(int size) {
+ return new SatelliteCapabilities[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SupportedRadioTechnology:");
+ if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) {
+ for (int technology : mSupportedRadioTechnologies) {
+ sb.append(technology);
+ sb.append(",");
+ }
+ } else {
+ sb.append("none,");
+ }
+
+ sb.append("SupportedFeatures:");
+ if (mSupportedFeatures != null && !mSupportedFeatures.isEmpty()) {
+ for (int feature : mSupportedFeatures) {
+ sb.append(feature);
+ sb.append(",");
+ }
+ } else {
+ sb.append("none,");
+ }
+
+ sb.append("isAlwaysOn:");
+ sb.append(mIsAlwaysOn);
+ sb.append(",");
+
+ sb.append("needsPointingToSatellite:");
+ sb.append(mNeedsPointingToSatellite);
+ sb.append(",");
+
+ sb.append("needsSeparateSimProfile:");
+ sb.append(mNeedsSeparateSimProfile);
+ return sb.toString();
+ }
+
+ @NonNull
+ public Set<Integer> getSupportedRadioTechnologies() {
+ return mSupportedRadioTechnologies;
+ }
+
+ public boolean isAlwaysOn() {
+ return mIsAlwaysOn;
+ }
+
+ /** Get function for mNeedsPointingToSatellite */
+ public boolean needsPointingToSatellite() {
+ return mNeedsPointingToSatellite;
+ }
+
+ @NonNull
+ public Set<Integer> getSupportedFeatures() {
+ return mSupportedFeatures;
+ }
+
+ /** Get function for mNeedsSeparateSimProfile */
+ public boolean needsSeparateSimProfile() {
+ return mNeedsSeparateSimProfile;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mSupportedRadioTechnologies = new HashSet<>();
+ int numSupportedRadioTechnologies = in.readInt();
+ if (numSupportedRadioTechnologies > 0) {
+ for (int i = 0; i < numSupportedRadioTechnologies; i++) {
+ mSupportedRadioTechnologies.add(in.readInt());
+ }
+ }
+
+ mIsAlwaysOn = in.readBoolean();
+ mNeedsPointingToSatellite = in.readBoolean();
+
+ mSupportedFeatures = new HashSet<>();
+ int numSupportedFeatures = in.readInt();
+ if (numSupportedFeatures > 0) {
+ for (int i = 0; i < numSupportedFeatures; i++) {
+ mSupportedFeatures.add(in.readInt());
+ }
+ }
+
+ mNeedsSeparateSimProfile = in.readBoolean();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
new file mode 100644
index 0000000..d3964a8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -0,0 +1,110 @@
+/*
+ * 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 android.telephony.satellite.stub;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * @hide
+ */
+public class SatelliteImplBase {
+ private static final String TAG = "SatelliteImplBase";
+
+ /**@hide*/
+ @IntDef(
+ prefix = "TECHNOLOGY_",
+ value = {
+ TECHNOLOGY_NB_IOT_NTN,
+ TECHNOLOGY_NR_NTN,
+ TECHNOLOGY_EMTC_NTN,
+ TECHNOLOGY_PROPRIETARY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NTRadioTechnology {}
+
+ /** 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology */
+ public static final int TECHNOLOGY_NB_IOT_NTN =
+ android.hardware.radio.satellite.NTRadioTechnology.NB_IOT_NTN;
+ /** 3GPP 5G NR over Non-Terrestrial-Networks technology */
+ public static final int TECHNOLOGY_NR_NTN =
+ android.hardware.radio.satellite.NTRadioTechnology.NR_NTN;
+ /** 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology */
+ public static final int TECHNOLOGY_EMTC_NTN =
+ android.hardware.radio.satellite.NTRadioTechnology.EMTC_NTN;
+ /** Proprietary technology like Iridium or Bullitt */
+ public static final int TECHNOLOGY_PROPRIETARY =
+ android.hardware.radio.satellite.NTRadioTechnology.PROPRIETARY;
+
+ /**@hide*/
+ @IntDef(
+ prefix = "FEATURE_",
+ value = {
+ FEATURE_SOS_SMS,
+ FEATURE_EMERGENCY_SMS,
+ FEATURE_SMS,
+ FEATURE_LOCATION_SHARING,
+ FEATURE_UNKNOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Feature {}
+
+ /** Able to send and receive SMS messages to/from SOS numbers like call/service centers */
+ public static final int FEATURE_SOS_SMS =
+ android.hardware.radio.satellite.SatelliteFeature.SOS_SMS;
+ /** Able to send and receive SMS messages to/from emergency numbers like 911 */
+ public static final int FEATURE_EMERGENCY_SMS =
+ android.hardware.radio.satellite.SatelliteFeature.EMERGENCY_SMS;
+ /** Able to send and receive SMS messages to/from any allowed contacts */
+ public static final int FEATURE_SMS = android.hardware.radio.satellite.SatelliteFeature.SMS;
+ /** Able to send device location to allowed contacts */
+ public static final int FEATURE_LOCATION_SHARING =
+ android.hardware.radio.satellite.SatelliteFeature.LOCATION_SHARING;
+ /** This feature is not defined in satellite HAL APIs */
+ public static final int FEATURE_UNKNOWN = 0xFFFF;
+
+ /**@hide*/
+ @IntDef(
+ prefix = "MODE_",
+ value = {
+ MODE_POWERED_OFF,
+ MODE_OUT_OF_SERVICE_NOT_SEARCHING,
+ MODE_OUT_OF_SERVICE_SEARCHING,
+ MODE_ACQUIRED,
+ MODE_MESSAGE_TRANSFERRING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mode {}
+
+ /** Satellite modem is powered off */
+ public static final int MODE_POWERED_OFF =
+ android.hardware.radio.satellite.SatelliteMode.POWERED_OFF;
+ /** Satellite modem is in out of service state and not searching for satellite signal */
+ public static final int MODE_OUT_OF_SERVICE_NOT_SEARCHING =
+ android.hardware.radio.satellite.SatelliteMode.OUT_OF_SERVICE_NOT_SEARCHING;
+ /** Satellite modem is in out of service state and searching for satellite signal */
+ public static final int MODE_OUT_OF_SERVICE_SEARCHING =
+ android.hardware.radio.satellite.SatelliteMode.OUT_OF_SERVICE_SEARCHING;
+ /** Satellite modem has found satellite signal and gets connected to the satellite network */
+ public static final int MODE_ACQUIRED = android.hardware.radio.satellite.SatelliteMode.ACQUIRED;
+ /** Satellite modem is sending and/or receiving messages */
+ public static final int MODE_MESSAGE_TRANSFERRING =
+ android.hardware.radio.satellite.SatelliteMode.MESSAGE_TRANSFERRING;
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c7ed347..6099cb1 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2687,4 +2687,9 @@
* @return {@code true} if the domain selection service is supported.
*/
boolean isDomainSelectionSupported();
+
+ /**
+ * Get the carrier restriction status of the device.
+ */
+ void getCarrierRestrictionStatus(IIntegerConsumer internalCallback, String packageName);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index a9af199..6e56963 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -546,6 +546,22 @@
int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+ int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+ int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
+ int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245;
+ int RIL_REQUEST_SET_SATELLITE_POWER = 246;
+ int RIL_REQUEST_GET_SATELLITE_POWER = 247;
+ int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248;
+ int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249;
+ int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250;
+ int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251;
+ int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252;
+ int RIL_REQUEST_GET_SATELLITE_MODE = 253;
+ int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254;
+ int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255;
+ int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256;
+ int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
+ int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -607,6 +623,13 @@
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
+ int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056;
+ int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057;
+ int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058;
+ int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059;
+ int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060;
+ int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061;
+ int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;
/* The following unsols are not defined in RIL.h */
int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 8fe6aac..18792a8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -103,6 +103,54 @@
}
/**
+ * Minimizes the PIP window my using the pinch in gesture.
+ *
+ * @param percent The percentage by which to decrease the pip window size.
+ * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
+ */
+ fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
+ // the percentage must be between 0.0f and 1.0f
+ if (percent <= 0.0f || percent > 1.0f) {
+ throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
+ }
+
+ val windowRect = getWindowRect(wmHelper)
+
+ // first pointer's initial x coordinate is halfway between the left edge and the center
+ val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat()
+ // second pointer's initial x coordinate is halfway between the right edge and the center
+ val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat()
+
+ // decrease by the distance specified through the percentage
+ val distDecrease = windowRect.width * percent
+
+ // get the final x-coordinates and make sure they are not passing the center of the window
+ val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat())
+ val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat())
+
+ // y-coordinate is the same throughout this animation
+ val yCoord = windowRect.centerY().toFloat()
+
+ var adjustedSteps = MIN_STEPS_TO_ANIMATE
+
+ // if distance per step is at least 1, then we can use the number of steps requested
+ if (distDecrease.toInt() / (steps * 2) >= 1) {
+ adjustedSteps = steps
+ }
+
+ // if the distance per step is less than 1, carry out the animation in two steps
+ gestureHelper.pinch(
+ Tuple(initLeftX, yCoord),
+ Tuple(initRightX, yCoord),
+ Tuple(finalLeftX, yCoord),
+ Tuple(finalRightX, yCoord),
+ adjustedSteps
+ )
+
+ waitForPipWindowToMinimizeFrom(wmHelper, Region.from(windowRect))
+ }
+
+ /**
* Launches the app through an intent instead of interacting with the launcher and waits until
* the app window is in PIP mode
*/
@@ -238,6 +286,24 @@
.waitForAndVerify()
}
+ private fun waitForPipWindowToMinimizeFrom(
+ wmHelper: WindowManagerStateHelper,
+ windowRect: Region
+ ) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("pipWindowMinimized") {
+ val pipAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ this.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val pipRegion = pipAppWindow.frameRegion
+ return@add windowRect.coversMoreThan(pipRegion)
+ }
+ .waitForAndVerify()
+ }
+
companion object {
private const val TAG = "PipAppHelper"
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 616f21cb..fdba9a6 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -1163,5 +1163,13 @@
<category android:name="com.android.test.hwui.TEST"/>
</intent-filter>
</activity>
+ <activity android:name="MeshLargeActivity"
+ android:label="Mesh/LargeMesh"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java
new file mode 100644
index 0000000..f97d942
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java
@@ -0,0 +1,175 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Mesh;
+import android.graphics.MeshSpecification;
+import android.graphics.MeshSpecification.Attribute;
+import android.graphics.MeshSpecification.Varying;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.View;
+
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+
+public class MeshLargeActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(new MeshView(this));
+ }
+
+ static class MeshView extends View {
+ MeshView(Context c) {
+ super(c);
+ this.setOnTouchListener((v, event) -> {
+ invalidate();
+ return true;
+ });
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ MeshSpecification meshSpec = createMeshSpecification();
+ int numTriangles = 10000;
+ // number of triangles plus first 2 vertices
+ FloatBuffer vertexBuffer = FloatBuffer.allocate((numTriangles + 2) * 30);
+ ShortBuffer indexBuffer = ShortBuffer.allocate(numTriangles * 3);
+
+ float origin = 500.0f;
+ int radius = 200;
+
+ // origin
+ vertexBuffer.put(0, origin);
+ vertexBuffer.put(1, origin);
+ for (int i = 0; i < 7; i++) {
+ vertexBuffer.put(2 + (i * 4), 1.0f);
+ vertexBuffer.put(2 + (i * 4) + 1, 1.0f);
+ vertexBuffer.put(2 + (i * 4) + 2, 1.0f);
+ vertexBuffer.put(2 + (i * 4) + 3, 1.0f);
+ }
+
+ // first point
+ vertexBuffer.put(30, origin + radius);
+ vertexBuffer.put(31, origin);
+ for (int i = 0; i < 7; i++) {
+ vertexBuffer.put(32 + (i * 4), 1.0f);
+ vertexBuffer.put(32 + (i * 4) + 1, 1.0f);
+ vertexBuffer.put(32 + (i * 4) + 2, 1.0f);
+ vertexBuffer.put(32 + (i * 4) + 3, 1.0f);
+ }
+
+ int nVert = 2;
+ int nInd = 0;
+ for (int i = 2; i <= numTriangles + 1; i++) {
+ double angle = 2 * Math.PI * i / numTriangles;
+ double x = radius * Math.cos(angle);
+ double y = radius * Math.sin(angle);
+ // position
+ vertexBuffer.put(i * 30, origin + (float) x);
+ vertexBuffer.put(i * 30 + 1, origin + (float) y);
+
+ // test through test7
+ for (int j = 0; j < 7; j++) {
+ vertexBuffer.put((i * 30 + 2) + (j * 4), 1.0f);
+ vertexBuffer.put((i * 30 + 2) + (j * 4) + 1, 1.0f);
+ vertexBuffer.put((i * 30 + 2) + (j * 4) + 2, 1.0f);
+ vertexBuffer.put((i * 30 + 2) + (j * 4) + 3, 1.0f);
+ }
+
+ indexBuffer.put(nInd++, (short) 0);
+ indexBuffer.put(nInd++, (short) (nVert - 1));
+ indexBuffer.put(nInd++, (short) nVert);
+ nVert++;
+ }
+ vertexBuffer.rewind();
+ indexBuffer.rewind();
+ Mesh mesh = Mesh.makeIndexed(
+ meshSpec, Mesh.TRIANGLES, vertexBuffer, numTriangles + 2, indexBuffer,
+ new Rect(0, 0, 1000, 1000)
+ );
+ mesh.setFloatUniform("test", 1.0f, 2.0f);
+ Paint paint = new Paint();
+ paint.setColor(Color.BLUE);
+
+ canvas.drawMesh(mesh, BlendMode.SRC, paint);
+ }
+
+ private MeshSpecification createMeshSpecification() {
+ String vs = "Varyings main(const Attributes attributes) { "
+ + " Varyings varyings;"
+ + " varyings.position = attributes.position;"
+ + " return varyings;"
+ + "}";
+ String fs = "uniform float2 test;"
+ + "float2 main(const Varyings varyings, out float4 color) {\n"
+ + " color = vec4(1.0, 0.0, 0.0, 1.0);"
+ + " return varyings.position;\n"
+ + "}";
+ ArrayList<Attribute> attList = new ArrayList<>();
+ attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position"));
+ attList.add(new Attribute(
+ MeshSpecification.FLOAT4,
+ 8,
+ "test"
+ ));
+ attList.add(new Attribute(
+ MeshSpecification.FLOAT4,
+ 24,
+ "test2"
+ ));
+ attList.add(new Attribute(
+ MeshSpecification.FLOAT4,
+ 40,
+ "test3"
+ ));
+ attList.add(new Attribute(
+ MeshSpecification.FLOAT4,
+ 56,
+ "test4"
+ ));
+ attList.add(new Attribute(
+ MeshSpecification.FLOAT4,
+ 72,
+ "test5"
+ ));
+ attList.add(new Attribute(
+ MeshSpecification.FLOAT4,
+ 88,
+ "test6"
+ ));
+ attList.add(new Attribute(
+ MeshSpecification.FLOAT4,
+ 104,
+ "test7"
+ ));
+ ArrayList<Varying> varyList = new ArrayList<>();
+ return MeshSpecification.make(attList, 120, varyList, vs, fs);
+ }
+ }
+}
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index ddcc811..d075b5f 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
+import android.hardware.input.HostUsiVersion;
import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,6 +58,8 @@
assertEquals(device.getKeyboardLanguageTag(), outDevice.getKeyboardLanguageTag());
assertEquals(device.getKeyboardLayoutType(), outDevice.getKeyboardLayoutType());
assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size());
+ assertEquals(device.getHostUsiVersion(), outDevice.getHostUsiVersion());
+ assertEquals(device.getAssociatedDisplayId(), outDevice.getAssociatedDisplayId());
KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap();
KeyCharacterMap outKeyCharacterMap = outDevice.getKeyCharacterMap();
@@ -88,7 +91,7 @@
.setHasBattery(true)
.setKeyboardLanguageTag("en-US")
.setKeyboardLayoutType("qwerty")
- .setSupportsUsi(true);
+ .setUsiVersion(new HostUsiVersion(2, 0));
for (int i = 0; i < 30; i++) {
deviceBuilder.addMotionRange(
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index bf6ece1..e704cc2 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -17,8 +17,10 @@
package com.android.internal.app;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.os.LocaleList;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -27,23 +29,21 @@
import com.android.internal.app.LocaleStore.LocaleInfo;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.IllformedLocaleException;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
-/**
- * Unit tests for the {@link LocaleStore}.
- */
+/** Unit tests for the {@link LocaleStore}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LocaleStoreTest {
- @Before
- public void setUp() {
- }
-
@Test
public void testTransformImeLanguageTagToLocaleInfo() {
List<InputMethodSubtype> list = List.of(
@@ -60,4 +60,99 @@
assertTrue(expectedLanguageTag.contains(info.getId()));
}
}
+
+ @Test
+ public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(
+ LocaleList.getEmptyLocaleList(), supportedLocale);
+
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ public void convertExplicitLocales_hasEmptyLocale_receiveException() {
+ Locale[] locales = {Locale.forLanguageTag(""), Locale.forLanguageTag("en-US")};
+ LocaleList localelist = new LocaleList(locales);
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ boolean isReceiveException = false;
+ try {
+ LocaleStore.convertExplicitLocales(localelist, supportedLocale);
+ } catch (IllformedLocaleException e) {
+ isReceiveException = true;
+ }
+
+ assertTrue(isReceiveException);
+ }
+
+ @Test
+ public void convertExplicitLocales_hasSameLocale_returnNonSameLocales() {
+ LocaleList locales = LocaleList.forLanguageTags("en-US,en-US");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ // Only has "en" and "en-US".
+ assertTrue(result.size() == 2);
+ }
+
+ @Test
+ public void convertExplicitLocales_hasEnUs_resultHasParentEn() {
+ LocaleList locales = LocaleList.forLanguageTags("en-US,ja-JP");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ assertEquals(result.get("en").getId(), "en");
+ }
+
+ @Test
+ public void convertExplicitLocales_hasZhTw_resultZhHantTw() {
+ LocaleList locales = LocaleList.forLanguageTags("zh-TW,en-US,en");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ assertEquals("zh-Hant-TW", result.get("zh-Hant-TW").getId());
+ }
+
+ @Test
+ public void convertExplicitLocales_nonRegularFormat_resultEmptyContry() {
+ LocaleList locales = LocaleList.forLanguageTags("de-1996,de-1901");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ assertEquals("de-1996", result.get("de-1996").getId());
+ assertTrue(result.get("de-1996").getLocale().getCountry().isEmpty());
+ }
+
+ @Test
+ public void convertExplicitLocales_differentEnFormat() {
+ LocaleList locales = LocaleList.forLanguageTags("en-Latn-US,en-US,en");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ assertEquals("en", result.get("en").getId());
+ assertEquals("en-US", result.get("en-US").getId());
+ assertNull(result.get("en-Latn-US"));
+ }
+
+ private ArrayList<LocaleInfo> getFakeSupportedLocales() {
+ String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
+ ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
+ for (String localeTag : locales) {
+ supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));
+ }
+ return supportedLocales;
+ }
}
diff --git a/tests/VectorDrawableTest/Android.bp b/tests/VectorDrawableTest/Android.bp
index 099d874..9da7c5f 100644
--- a/tests/VectorDrawableTest/Android.bp
+++ b/tests/VectorDrawableTest/Android.bp
@@ -26,7 +26,5 @@
android_test {
name: "VectorDrawableTest",
srcs: ["**/*.java"],
- // certificate set as platform to allow testing of @hidden APIs
- certificate: "platform",
platform_apis: true,
}
diff --git a/tests/VectorDrawableTest/AndroidManifest.xml b/tests/VectorDrawableTest/AndroidManifest.xml
index 163e438..5334dac 100644
--- a/tests/VectorDrawableTest/AndroidManifest.xml
+++ b/tests/VectorDrawableTest/AndroidManifest.xml
@@ -158,15 +158,6 @@
<category android:name="com.android.test.dynamic.TEST"/>
</intent-filter>
</activity>
- <activity android:name="LottieDrawableTest"
- android:label="Lottie test bed"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="com.android.test.dynamic.TEST" />
- </intent-filter>
- </activity>
</application>
</manifest>
diff --git a/tests/VectorDrawableTest/res/raw/lottie.json b/tests/VectorDrawableTest/res/raw/lottie.json
deleted file mode 100644
index fea571c..0000000
--- a/tests/VectorDrawableTest/res/raw/lottie.json
+++ /dev/null
@@ -1,123 +0,0 @@
-{
- "v":"4.6.9",
- "fr":60,
- "ip":0,
- "op":200,
- "w":800,
- "h":600,
- "nm":"Loader 1 JSON",
- "ddd":0,
-
-
- "layers":[
- {
- "ddd":0,
- "ind":1,
- "ty":4,
- "nm":"Custom Path 1",
- "ao": 0,
- "ip": 0,
- "op": 300,
- "st": 0,
- "sr": 1,
- "bm": 0,
- "ks": {
- "o": { "a":0, "k":100 },
- "r": { "a":1, "k": [
- { "s": [ 0 ], "e": [ 360], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 },
- { "t": 200 }
- ] },
- "p": { "a":0, "k":[ 300, 300, 0 ] },
- "a": { "a":0, "k":[ 100, 100, 0 ] },
- "s": { "a":1, "k":[
- { "s": [ 100, 100 ], "e": [ 200, 200 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 },
- { "s": [ 200, 200 ], "e": [ 100, 100 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 },
- { "t": 200 }
- ] }
- },
-
- "shapes":[
- {
- "ty":"gr",
- "it":[
- {
- "ty" : "sh",
- "nm" : "Path 1",
- "ks" : {
- "a" : 1,
- "k" : [
- {
- "s": [ {
- "i": [ [ 0, 50 ], [ -50, 0 ], [ 0, -50 ], [ 50, 0 ] ],
- "o": [ [ 0, -50 ], [ 50, 0 ], [ 0, 50 ], [ -50, 0 ] ],
- "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ],
- "c": true
- } ],
- "e": [ {
- "i": [ [ 50, 50 ], [ -50, 0 ], [ -50, -50 ], [ 50, 50 ] ],
- "o": [ [ 50, -50 ], [ 50, 0 ], [ -50, 50 ], [ -50, 50 ] ],
- "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ],
- "c": true
- } ],
- "i": { "x":0.5, "y":0.5 },
- "o": { "x":0.5, "y":0.5 },
- "t": 0
- },
- {
- "s": [ {
- "i": [ [ 50, 50 ], [ -50, 0 ], [ -50, -50 ], [ 50, 50 ] ],
- "o": [ [ 50, -50 ], [ 50, 0 ], [ -50, 50 ], [ -50, 50 ] ],
- "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ],
- "c": true
- } ],
- "e": [ {
- "i": [ [ 0, 50 ], [ -50, 0 ], [ 0, -50 ], [ 50, 0 ] ],
- "o": [ [ 0, -50 ], [ 50, 0 ], [ 0, 50 ], [ -50, 0 ] ],
- "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ],
- "c": true
- } ],
- "i": { "x":0.5, "y":0.5 },
- "o": { "x":0.5, "y":0.5 },
- "t": 100
- },
- {
- "t": 200
- }
- ]
- }
- },
-
- {
- "ty": "st",
- "nm": "Stroke 1",
- "lc": 1,
- "lj": 1,
- "ml": 4,
- "w" : { "a": 1, "k": [
- { "s": [ 30 ], "e": [ 50 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 },
- { "s": [ 50 ], "e": [ 30 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 },
- { "t": 200 }
- ] },
- "o" : { "a": 0, "k": 100 },
- "c" : { "a": 1, "k": [
- { "s": [ 0, 1, 0 ], "e": [ 1, 0, 0 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 },
- { "s": [ 1, 0, 0 ], "e": [ 0, 1, 0 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 },
- { "t": 200 }
- ] }
- },
-
- {
- "ty":"tr",
- "p" : { "a":0, "k":[ 0, 0 ] },
- "a" : { "a":0, "k":[ 0, 0 ] },
- "s" : { "a":0, "k":[ 100, 100 ] },
- "r" : { "a":0, "k": 0 },
- "o" : { "a":0, "k":100 },
- "nm": "Transform"
- }
- ]
- }
- ]
- }
- ]
- }
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java
deleted file mode 100644
index 05eae7b..0000000
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java
+++ /dev/null
@@ -1,76 +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.test.dynamic;
-
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.LottieDrawable;
-import android.os.Bundle;
-import android.view.View;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Scanner;
-
-@SuppressWarnings({"UnusedDeclaration"})
-public class LottieDrawableTest extends Activity {
- private static final String TAG = "LottieDrawableTest";
- static final int BACKGROUND = 0xFFF44336;
-
- class LottieDrawableView extends View {
- private Rect mLottieBounds;
-
- private LottieDrawable mLottie;
-
- LottieDrawableView(Context context, InputStream is) {
- super(context);
- Scanner s = new Scanner(is).useDelimiter("\\A");
- String json = s.hasNext() ? s.next() : "";
- try {
- mLottie = LottieDrawable.makeLottieDrawable(json);
- } catch (IOException e) {
- throw new RuntimeException(TAG + ": error parsing test Lottie");
- }
- mLottie.start();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- canvas.drawColor(BACKGROUND);
-
- mLottie.setBounds(mLottieBounds);
- mLottie.draw(canvas);
- }
-
- public void setLottieSize(Rect bounds) {
- mLottieBounds = bounds;
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- InputStream is = getResources().openRawResource(R.raw.lottie);
-
- LottieDrawableView view = new LottieDrawableView(this, is);
- view.setLottieSize(new Rect(0, 0, 900, 900));
- setContentView(view);
- }
-}
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index 965b073..34f884b 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -19,9 +19,6 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
-import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
-import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
-import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
@@ -143,6 +140,8 @@
@NonNull private TelephonySubscriptionTrackerCallback mCallback;
@NonNull private TelephonySubscriptionTracker mTelephonySubscriptionTracker;
+ @NonNull private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
+
public TelephonySubscriptionTrackerTest() {
mContext = mock(Context.class);
mTestLooper = new TestLooper();
@@ -173,7 +172,7 @@
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
doReturn(TEST_CARRIER_CONFIG)
.when(mCarrierConfigManager)
- .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1));
+ .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1), any());
// subId 1, 2 are in same subGrp, only subId 1 is active
doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_1).getGroupUuid();
@@ -189,9 +188,15 @@
doReturn(2).when(mTelephonyManager).getActiveModemCount();
mCallback = mock(TelephonySubscriptionTrackerCallback.class);
+ // Capture CarrierConfigChangeListener to emulate the carrier config change notification
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
mTelephonySubscriptionTracker =
new TelephonySubscriptionTracker(mContext, mHandler, mCallback, mDeps);
mTelephonySubscriptionTracker.register();
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ listenerArgumentCaptor.capture());
+ mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
doReturn(true).when(mDeps).isConfigForIdentifiedCarrier(any());
doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2))
@@ -239,14 +244,11 @@
return intent;
}
- private Intent buildTestBroadcastIntent(boolean hasValidSubscription) {
- Intent intent = new Intent(ACTION_CARRIER_CONFIG_CHANGED);
- intent.putExtra(EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX);
- intent.putExtra(
- EXTRA_SUBSCRIPTION_INDEX,
- hasValidSubscription ? TEST_SUBSCRIPTION_ID_1 : INVALID_SUBSCRIPTION_ID);
-
- return intent;
+ private void sendCarrierConfigChange(boolean hasValidSubscription) {
+ mCarrierConfigChangeListener.onCarrierConfigChanged(
+ TEST_SIM_SLOT_INDEX,
+ hasValidSubscription ? TEST_SUBSCRIPTION_ID_1 : INVALID_SUBSCRIPTION_ID,
+ TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
}
private TelephonySubscriptionSnapshot buildExpectedSnapshot(
@@ -302,14 +304,15 @@
any(),
eq(mHandler));
final IntentFilter filter = getIntentFilter();
- assertEquals(2, filter.countActions());
- assertTrue(filter.hasAction(ACTION_CARRIER_CONFIG_CHANGED));
+ assertEquals(1, filter.countActions());
assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED));
verify(mSubscriptionManager)
.addOnSubscriptionsChangedListener(any(HandlerExecutor.class), any());
assertNotNull(getOnSubscriptionsChangedListener());
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(), any());
+
verify(mTelephonyManager, times(2))
.registerCarrierPrivilegesCallback(anyInt(), any(HandlerExecutor.class), any());
verify(mTelephonyManager)
@@ -442,7 +445,7 @@
@Test
public void testReceiveBroadcast_ConfigReadyWithSubscriptions() throws Exception {
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ sendCarrierConfigChange(true /* hasValidSubscription */);
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
@@ -454,7 +457,7 @@
.when(mSubscriptionManager)
.getAllSubscriptionInfoList();
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ sendCarrierConfigChange(true /* hasValidSubscription */);
mTestLooper.dispatchAll();
// Expect an empty snapshot
@@ -465,7 +468,7 @@
public void testReceiveBroadcast_SlotCleared() throws Exception {
setupReadySubIds();
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
+ sendCarrierConfigChange(false /* hasValidSubscription */);
mTestLooper.dispatchAll();
verifyNoActiveSubscriptions();
@@ -476,7 +479,7 @@
public void testReceiveBroadcast_ConfigNotReady() throws Exception {
doReturn(false).when(mDeps).isConfigForIdentifiedCarrier(any());
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ sendCarrierConfigChange(true /* hasValidSubscription */);
mTestLooper.dispatchAll();
// No interactions expected; config was not loaded
@@ -485,21 +488,21 @@
@Test
public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception {
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ sendCarrierConfigChange(true /* hasValidSubscription */);
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
assertNotNull(
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList();
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ sendCarrierConfigChange(true /* hasValidSubscription */);
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap())));
}
@Test
public void testCarrierConfigUpdatedAfterValidTriggersCallbacks() throws Exception {
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ sendCarrierConfigChange(true /* hasValidSubscription */);
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
reset(mCallback);
@@ -510,12 +513,12 @@
new int[] {TRANSPORT_WIFI, TRANSPORT_CELLULAR});
doReturn(updatedConfig)
.when(mCarrierConfigManager)
- .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1));
+ .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1), any());
Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap = new HashMap<>();
subIdToCarrierConfigMap.put(
TEST_SUBSCRIPTION_ID_1, new PersistableBundleWrapper(updatedConfig));
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ sendCarrierConfigChange(true /* hasValidSubscription */);
mTestLooper.dispatchAll();
verify(mCallback)
@@ -530,13 +533,13 @@
@Test
public void testSlotClearedAfterValidTriggersCallbacks() throws Exception {
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ sendCarrierConfigChange(true /* hasValidSubscription */);
mTestLooper.dispatchAll();
verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
assertNotNull(
mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
- mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
+ sendCarrierConfigChange(false /* hasValidSubscription */);
mTestLooper.dispatchAll();
verify(mCallback)
.onNewSnapshot(