Merge "Revert "Properly notify batterystats when packages change idleness.""
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/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index fab5b5f..ad406a1 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -158,17 +158,11 @@
boolean mForceAllAppStandbyForSmallBattery;
/**
- * True if the forced app standby feature is enabled in settings
- */
- @GuardedBy("mLock")
- boolean mForcedAppStandbyEnabled;
-
- /**
* A lock-free set of (uid, packageName) pairs in background restricted mode.
*
* <p>
- * It's bascially shadowing the {@link #mRunAnyRestrictedPackages} together with
- * the {@link #mForcedAppStandbyEnabled} - mutations on them would result in copy-on-write.
+ * It's basically shadowing the {@link #mRunAnyRestrictedPackages}, any mutations on it would
+ * result in copy-on-write.
* </p>
*/
volatile Set<Pair<Integer, String>> mBackgroundRestrictedUidPackages = Collections.emptySet();
@@ -200,10 +194,9 @@
int TEMP_EXEMPTION_LIST_CHANGED = 5;
int EXEMPTED_BUCKET_CHANGED = 6;
int FORCE_ALL_CHANGED = 7;
- int FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8;
- int IS_UID_ACTIVE_CACHED = 9;
- int IS_UID_ACTIVE_RAW = 10;
+ int IS_UID_ACTIVE_CACHED = 8;
+ int IS_UID_ACTIVE_RAW = 9;
}
private final StatLogger mStatLogger = new StatLogger(new String[] {
@@ -215,7 +208,6 @@
"TEMP_EXEMPTION_LIST_CHANGED",
"EXEMPTED_BUCKET_CHANGED",
"FORCE_ALL_CHANGED",
- "FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED",
"IS_UID_ACTIVE_CACHED",
"IS_UID_ACTIVE_RAW",
@@ -228,18 +220,10 @@
}
void register() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED),
- false, this);
-
mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED), false, this);
}
- boolean isForcedAppStandbyEnabled() {
- return injectGetGlobalSettingInt(Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1;
- }
-
boolean isForcedAppStandbyForSmallBatteryEnabled() {
return injectGetGlobalSettingInt(
Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 0) == 1;
@@ -247,21 +231,7 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
- if (Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED).equals(uri)) {
- final boolean enabled = isForcedAppStandbyEnabled();
- synchronized (mLock) {
- if (mForcedAppStandbyEnabled == enabled) {
- return;
- }
- mForcedAppStandbyEnabled = enabled;
- updateBackgroundRestrictedUidPackagesLocked();
- if (DEBUG) {
- Slog.d(TAG, "Forced app standby feature flag changed: "
- + mForcedAppStandbyEnabled);
- }
- }
- mHandler.notifyForcedAppStandbyFeatureFlagChanged();
- } else if (Settings.Global.getUriFor(
+ if (Settings.Global.getUriFor(
Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED).equals(uri)) {
final boolean enabled = isForcedAppStandbyForSmallBatteryEnabled();
synchronized (mLock) {
@@ -515,7 +485,6 @@
mFlagsObserver = new FeatureFlagsObserver();
mFlagsObserver.register();
- mForcedAppStandbyEnabled = mFlagsObserver.isForcedAppStandbyEnabled();
mForceAllAppStandbyForSmallBattery =
mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled();
mStandbyTracker = new StandbyTracker();
@@ -636,14 +605,10 @@
/**
* Update the {@link #mBackgroundRestrictedUidPackages} upon mutations on
- * {@link #mRunAnyRestrictedPackages} or {@link #mForcedAppStandbyEnabled}.
+ * {@link #mRunAnyRestrictedPackages}.
*/
@GuardedBy("mLock")
private void updateBackgroundRestrictedUidPackagesLocked() {
- if (!mForcedAppStandbyEnabled) {
- mBackgroundRestrictedUidPackages = Collections.emptySet();
- return;
- }
Set<Pair<Integer, String>> fasUidPkgs = new ArraySet<>();
for (int i = 0, size = mRunAnyRestrictedPackages.size(); i < size; i++) {
fasUidPkgs.add(mRunAnyRestrictedPackages.valueAt(i));
@@ -821,13 +786,14 @@
private class MyHandler extends Handler {
private static final int MSG_UID_ACTIVE_STATE_CHANGED = 0;
+ // Unused ids 1, 2.
private static final int MSG_RUN_ANY_CHANGED = 3;
private static final int MSG_ALL_UNEXEMPTED = 4;
private static final int MSG_ALL_EXEMPTION_LIST_CHANGED = 5;
private static final int MSG_TEMP_EXEMPTION_LIST_CHANGED = 6;
private static final int MSG_FORCE_ALL_CHANGED = 7;
private static final int MSG_USER_REMOVED = 8;
- private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
+ // Unused id 9.
private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10;
private static final int MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED = 11;
@@ -867,11 +833,6 @@
obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
}
- public void notifyForcedAppStandbyFeatureFlagChanged() {
- removeMessages(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED);
- obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget();
- }
-
public void notifyExemptedBucketChanged() {
removeMessages(MSG_EXEMPTED_BUCKET_CHANGED);
obtainMessage(MSG_EXEMPTED_BUCKET_CHANGED).sendToTarget();
@@ -966,22 +927,6 @@
mStatLogger.logDurationStat(Stats.FORCE_ALL_CHANGED, start);
return;
- case MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED:
- // Feature flag for forced app standby changed.
- final boolean unblockAlarms;
- synchronized (mLock) {
- unblockAlarms = !mForcedAppStandbyEnabled;
- }
- for (Listener l : cloneListeners()) {
- l.updateAllJobs();
- if (unblockAlarms) {
- l.unblockAllUnrestrictedAlarms();
- }
- }
- mStatLogger.logDurationStat(
- Stats.FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED, start);
- return;
-
case MSG_USER_REMOVED:
handleUserRemoved(msg.arg1);
return;
@@ -1164,8 +1109,7 @@
// If apps will be put into restricted standby bucket automatically on user-forced
// app standby, instead of blocking alarms completely, let the restricted standby bucket
// policy take care of it.
- return (mForcedAppStandbyEnabled
- && !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ return (!mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
&& isRunAnyRestrictedLocked(uid, packageName));
}
}
@@ -1210,8 +1154,7 @@
// If apps will be put into restricted standby bucket automatically on user-forced
// app standby, instead of blocking jobs completely, let the restricted standby bucket
// policy take care of it.
- if (mForcedAppStandbyEnabled
- && !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ if (!mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
&& isRunAnyRestrictedLocked(uid, packageName)) {
return true;
}
@@ -1321,8 +1264,6 @@
pw.println("Current AppStateTracker State:");
pw.increaseIndent();
- pw.println("Forced App Standby Feature enabled: " + mForcedAppStandbyEnabled);
-
pw.print("Force all apps standby: ");
pw.println(isForceAllAppsStandbyEnabled());
@@ -1400,8 +1341,6 @@
synchronized (mLock) {
final long token = proto.start(fieldId);
- proto.write(AppStateTrackerProto.FORCED_APP_STANDBY_FEATURE_ENABLED,
- mForcedAppStandbyEnabled);
proto.write(AppStateTrackerProto.FORCE_ALL_APPS_STANDBY,
isForceAllAppsStandbyEnabled());
proto.write(AppStateTrackerProto.IS_SMALL_BATTERY_DEVICE, isSmallBatteryDevice());
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index e41eb00..37d190d 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -5379,9 +5379,7 @@
@Override
public void unblockAllUnrestrictedAlarms() {
- // Called when:
- // 1. Power exemption list changes,
- // 2. User FAS feature is disabled.
+ // Called when the power exemption list changes.
synchronized (mLock) {
sendAllUnrestrictedPendingBackgroundAlarmsLocked();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 43f7279..76336fb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -2545,6 +2545,7 @@
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus, debugStopReason) : null;
if (rescheduledJob != null
+ && !rescheduledJob.shouldTreatAsUserInitiatedJob()
&& (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT
|| debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) {
rescheduledJob.disallowRunInBatterySaverAndDoze();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index fb5d63e..c2168d2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -361,7 +361,14 @@
boolean binding = false;
try {
final int bindFlags;
- if (job.shouldTreatAsExpeditedJob()) {
+ if (job.shouldTreatAsUserInitiatedJob()) {
+ // TODO (191785864, 261999509): add an appropriate flag so user-initiated jobs
+ // can bypass data saver
+ bindFlags = Context.BIND_AUTO_CREATE
+ | Context.BIND_ALMOST_PERCEPTIBLE
+ | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
+ | Context.BIND_NOT_APP_COMPONENT_USAGE;
+ } else if (job.shouldTreatAsExpeditedJob()) {
bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
| Context.BIND_ALMOST_PERCEPTIBLE
| Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 16f5c7f..b87dec1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -98,6 +98,13 @@
~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
| ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
| ConnectivityManager.BLOCKED_REASON_DOZE);
+ // TODO(261999509): allow bypassing data saver & user-restricted. However, when we allow a UI
+ // job to run while data saver restricts the app, we must ensure that we don't run regular
+ // jobs when we put a hole in the data saver wall for the UI job
+ private static final int UNBYPASSABLE_UI_BLOCKED_REASONS =
+ ~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
+ | ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
+ | ConnectivityManager.BLOCKED_REASON_DOZE);
private static final int UNBYPASSABLE_FOREGROUND_BLOCKED_REASONS =
~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
| ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
@@ -1080,6 +1087,11 @@
Slog.d(TAG, "Using FG bypass for " + jobStatus.getSourceUid());
}
unbypassableBlockedReasons = UNBYPASSABLE_FOREGROUND_BLOCKED_REASONS;
+ } else if (jobStatus.shouldTreatAsUserInitiatedJob()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Using UI bypass for " + jobStatus.getSourceUid());
+ }
+ unbypassableBlockedReasons = UNBYPASSABLE_UI_BLOCKED_REASONS;
} else if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.startedAsExpeditedJob) {
if (DEBUG) {
Slog.d(TAG, "Using EJ bypass for " + jobStatus.getSourceUid());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index a2e8eb4..9a69fdf 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1411,16 +1411,18 @@
public boolean canRunInDoze() {
return appHasDozeExemption
|| (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
- || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
|| shouldTreatAsUserInitiatedJob()
- && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
+ // EJs can't run in Doze if we explicitly require that the device is not Dozing.
+ || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
+ && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
}
boolean canRunInBatterySaver() {
return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
- || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
|| shouldTreatAsUserInitiatedJob()
- && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
+ // EJs can't run in Battery Saver if we explicitly require that Battery Saver is off
+ || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
+ && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
}
/** @return true if the constraint was changed, false otherwise. */
diff --git a/core/api/current.txt b/core/api/current.txt
index c0e0eab..a91607a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4319,6 +4319,7 @@
method public void recreate();
method public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks);
method public void registerForContextMenu(android.view.View);
+ method @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_CAPTURE) public void registerScreenCaptureCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.Activity.ScreenCaptureCallback);
method public boolean releaseInstance();
method @Deprecated public final void removeDialog(int);
method public void reportFullyDrawn();
@@ -4404,6 +4405,7 @@
method public void triggerSearch(String, @Nullable android.os.Bundle);
method public void unregisterActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks);
method public void unregisterForContextMenu(android.view.View);
+ method @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_CAPTURE) public void unregisterScreenCaptureCallback(@NonNull android.app.Activity.ScreenCaptureCallback);
field public static final int DEFAULT_KEYS_DIALER = 1; // 0x1
field public static final int DEFAULT_KEYS_DISABLE = 0; // 0x0
field public static final int DEFAULT_KEYS_SEARCH_GLOBAL = 4; // 0x4
@@ -4417,6 +4419,10 @@
field public static final int RESULT_OK = -1; // 0xffffffff
}
+ public static interface Activity.ScreenCaptureCallback {
+ method public void onScreenCaptured();
+ }
+
@Deprecated public class ActivityGroup extends android.app.Activity {
ctor @Deprecated public ActivityGroup();
ctor @Deprecated public ActivityGroup(boolean);
@@ -12881,16 +12887,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);
@@ -19045,15 +19041,15 @@
method public int getSurfaceGroupId();
method @NonNull public java.util.List<android.view.Surface> getSurfaces();
method public int getTimestampBase();
- method public boolean isReadoutTimestampUsed();
+ method public boolean isReadoutTimestampEnabled();
method public void removeSensorPixelModeUsed(int);
method public void removeSurface(@NonNull android.view.Surface);
method public void setDynamicRangeProfile(long);
method public void setMirrorMode(int);
method public void setPhysicalCameraId(@Nullable String);
+ method public void setReadoutTimestampEnabled(boolean);
method public void setStreamUseCase(long);
method public void setTimestampBase(int);
- method public void useReadoutTimestamp(boolean);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
field public static final int MIRROR_MODE_AUTO = 0; // 0x0
@@ -20912,6 +20908,7 @@
method public void adjustStreamVolume(int, int, int);
method public void adjustSuggestedStreamVolume(int, int, int);
method public void adjustVolume(int, int);
+ method public void adjustVolumeGroupVolume(int, int, int);
method public void clearCommunicationDevice();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public boolean clearPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo);
method public void dispatchMediaKeyEvent(android.view.KeyEvent);
@@ -20942,6 +20939,7 @@
method public float getStreamVolumeDb(int, int, int);
method @NonNull public java.util.List<android.media.AudioMixerAttributes> getSupportedMixerAttributes(@NonNull android.media.AudioDeviceInfo);
method @Deprecated public int getVibrateSetting(int);
+ method public int getVolumeGroupIdForAttributes(@NonNull android.media.AudioAttributes);
method @Deprecated public boolean isBluetoothA2dpOn();
method public boolean isBluetoothScoAvailableOffCall();
method @Deprecated public boolean isBluetoothScoOn();
@@ -20955,6 +20953,7 @@
method public boolean isStreamMute(int);
method public boolean isSurroundFormatEnabled(int);
method public boolean isVolumeFixed();
+ method public boolean isVolumeGroupMuted(int);
method @Deprecated public boolean isWiredHeadsetOn();
method public void loadSoundEffects();
method public void playSoundEffect(int);
@@ -24036,6 +24035,7 @@
method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
+ method public void showSystemOutputSwitcher();
method public void stop();
method public void transferTo(@NonNull android.media.MediaRoute2Info);
method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
@@ -33225,7 +33225,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);
@@ -35376,7 +35375,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;
@@ -35397,7 +35396,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";
@@ -35430,7 +35429,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";
@@ -35476,7 +35475,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";
@@ -35495,7 +35494,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";
@@ -35542,7 +35541,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";
@@ -35566,7 +35565,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";
@@ -35597,7 +35596,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";
@@ -52963,10 +52962,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 {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d3775ad..a1671ab 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3908,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 isCredentialShareableWithParent();
+ method public boolean isMediaSharedWithParent();
+ 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 {
@@ -4588,8 +4596,7 @@
}
public final class HdmiPortInfo implements android.os.Parcelable {
- ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean);
- ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean, boolean);
+ ctor @Deprecated public HdmiPortInfo(int, int, int, boolean, boolean, boolean);
method public int describeContents();
method public int getAddress();
method public int getId();
@@ -4604,6 +4611,15 @@
field public static final int PORT_OUTPUT = 1; // 0x1
}
+ public static final class HdmiPortInfo.Builder {
+ ctor public HdmiPortInfo.Builder(int, int, int);
+ method @NonNull public android.hardware.hdmi.HdmiPortInfo build();
+ method @NonNull public android.hardware.hdmi.HdmiPortInfo.Builder setArcSupported(boolean);
+ method @NonNull public android.hardware.hdmi.HdmiPortInfo.Builder setCecSupported(boolean);
+ method @NonNull public android.hardware.hdmi.HdmiPortInfo.Builder setEarcSupported(boolean);
+ method @NonNull public android.hardware.hdmi.HdmiPortInfo.Builder setMhlSupported(boolean);
+ }
+
public abstract class HdmiRecordListener {
ctor public HdmiRecordListener();
method public void onClearTimerRecordingResult(int, int);
@@ -6672,6 +6688,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int);
+ method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleVolumeGroupVolume(int);
method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
@@ -6681,6 +6698,9 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
+ method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int getVolumeGroupMaxVolumeIndex(int);
+ method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int getVolumeGroupMinVolumeIndex(int);
+ method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int getVolumeGroupVolumeIndex(int);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method public boolean isAudioServerRunning();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isBluetoothVariableLatencyEnabled();
@@ -6714,6 +6734,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public void setVolumeGroupVolumeIndex(int, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean supportsBluetoothVariableLatency();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -7120,6 +7141,7 @@
method public int describeContents();
method @NonNull public android.media.AudioAttributes getAudioAttributes();
method public int getId();
+ method @NonNull public String getName();
method public boolean supportsAudioAttributes(@NonNull android.media.AudioAttributes);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioProductStrategy> CREATOR;
@@ -8759,6 +8781,7 @@
field public static final int TYPE_DVBC = 4; // 0x4
field public static final int TYPE_DVBS = 5; // 0x5
field public static final int TYPE_DVBT = 6; // 0x6
+ field public static final int TYPE_IPTV = 11; // 0xb
field public static final int TYPE_ISDBS = 7; // 0x7
field public static final int TYPE_ISDBS3 = 8; // 0x8
field public static final int TYPE_ISDBT = 9; // 0x9
@@ -8781,6 +8804,11 @@
method public int getHierarchy();
method public long getInnerFec();
method @NonNull public int[] getInterleaving();
+ method @IntRange(from=0) public int getIptvAverageJitterMillis();
+ method @NonNull public String getIptvContentUrl();
+ method @IntRange(from=0) public long getIptvPacketsLost();
+ method @IntRange(from=0) public long getIptvPacketsReceived();
+ method @IntRange(from=0) public int getIptvWorstJitterMillis();
method public int getIsdbtMode();
method public int getIsdbtPartialReceptionFlag();
method @IntRange(from=0, to=255) @NonNull public int[] getIsdbtSegment();
@@ -8824,6 +8852,11 @@
field public static final int FRONTEND_STATUS_TYPE_GUARD_INTERVAL = 26; // 0x1a
field public static final int FRONTEND_STATUS_TYPE_HIERARCHY = 19; // 0x13
field public static final int FRONTEND_STATUS_TYPE_INTERLEAVINGS = 30; // 0x1e
+ field public static final int FRONTEND_STATUS_TYPE_IPTV_AVERAGE_JITTER_MS = 46; // 0x2e
+ field public static final int FRONTEND_STATUS_TYPE_IPTV_CONTENT_URL = 42; // 0x2a
+ field public static final int FRONTEND_STATUS_TYPE_IPTV_PACKETS_LOST = 43; // 0x2b
+ field public static final int FRONTEND_STATUS_TYPE_IPTV_PACKETS_RECEIVED = 44; // 0x2c
+ field public static final int FRONTEND_STATUS_TYPE_IPTV_WORST_JITTER_MS = 45; // 0x2d
field public static final int FRONTEND_STATUS_TYPE_ISDBT_MODE = 37; // 0x25
field public static final int FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG = 38; // 0x26
field public static final int FRONTEND_STATUS_TYPE_ISDBT_SEGMENTS = 31; // 0x1f
@@ -8869,6 +8902,60 @@
field public static final int FRONTEND_STATUS_READINESS_UNSUPPORTED = 4; // 0x4
}
+ public class IptvFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
+ ctor public IptvFrontendSettings(@NonNull byte[], @NonNull byte[], int, int, @NonNull android.media.tv.tuner.frontend.IptvFrontendSettingsFec, int, int, long, @NonNull String);
+ method @NonNull public static android.media.tv.tuner.frontend.IptvFrontendSettings.Builder builder();
+ method @IntRange(from=0) public long getBitrate();
+ method @NonNull public String getContentUrl();
+ method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
+ method public int getDstPort();
+ method @Nullable public android.media.tv.tuner.frontend.IptvFrontendSettingsFec getFec();
+ method public int getIgmp();
+ method public int getProtocol();
+ method @NonNull @Size(min=4, max=16) public byte[] getSrcIpAddress();
+ method public int getSrcPort();
+ method public int getType();
+ field public static final int IGMP_UNDEFINED = 0; // 0x0
+ field public static final int IGMP_V1 = 1; // 0x1
+ field public static final int IGMP_V2 = 2; // 0x2
+ field public static final int IGMP_V3 = 4; // 0x4
+ field public static final int PROTOCOL_RTP = 2; // 0x2
+ field public static final int PROTOCOL_UDP = 1; // 0x1
+ field public static final int PROTOCOL_UNDEFINED = 0; // 0x0
+ }
+
+ public static final class IptvFrontendSettings.Builder {
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings build();
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setBitrate(@IntRange(from=0) long);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setContentUrl(@NonNull String);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setDstIpAddress(@NonNull byte[]);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setDstPort(int);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setFec(@Nullable android.media.tv.tuner.frontend.IptvFrontendSettingsFec);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setIgmp(int);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setProtocol(int);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setSrcIpAddress(@NonNull byte[]);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettings.Builder setSrcPort(int);
+ }
+
+ public class IptvFrontendSettingsFec {
+ ctor public IptvFrontendSettingsFec(int, int, int);
+ method @NonNull public static android.media.tv.tuner.frontend.IptvFrontendSettingsFec.Builder builder();
+ method @IntRange(from=0) public int getFecColNum();
+ method @IntRange(from=0) public int getFecRowNum();
+ method public int getFecType();
+ field public static final int FEC_TYPE_COLUMN = 1; // 0x1
+ field public static final int FEC_TYPE_COLUMN_ROW = 4; // 0x4
+ field public static final int FEC_TYPE_ROW = 2; // 0x2
+ field public static final int FEC_TYPE_UNDEFINED = 0; // 0x0
+ }
+
+ public static final class IptvFrontendSettingsFec.Builder {
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettingsFec build();
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettingsFec.Builder setFecColNum(@IntRange(from=0) int);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettingsFec.Builder setFecRowNum(@IntRange(from=0) int);
+ method @NonNull public android.media.tv.tuner.frontend.IptvFrontendSettingsFec.Builder setFecType(int);
+ }
+
public class Isdbs3FrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
method public int getCodeRateCapability();
method public int getModulationCapability();
@@ -10415,6 +10502,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();
@@ -10423,11 +10511,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();
@@ -16566,6 +16654,7 @@
public interface WindowManager extends android.view.ViewManager {
method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public android.graphics.Region getCurrentImeTouchRegion();
+ method @NonNull public default java.util.List<android.content.ComponentName> notifyScreenshotListeners(int);
method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback);
method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index da9697b..8dc7dc3 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -946,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 {
@@ -975,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 {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c0239e8..3c17a33 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -26,6 +27,7 @@
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
+import android.annotation.CallbackExecutor;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.IntDef;
@@ -1016,6 +1018,7 @@
private ComponentCallbacksController mCallbacksController;
@Nullable private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+ private ScreenCaptureCallbackHandler mScreenCaptureCallbackHandler;
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
@@ -9222,4 +9225,43 @@
}
return mWindow.getOnBackInvokedDispatcher();
}
+
+ /**
+ * Interface for observing screen captures of an {@link Activity}.
+ */
+ public interface ScreenCaptureCallback {
+ /**
+ * Called when one of the monitored activities is captured.
+ * This is not invoked if the activity window
+ * has {@link WindowManager.LayoutParams#FLAG_SECURE} set.
+ */
+ void onScreenCaptured();
+ }
+
+ /**
+ * Registers a screen capture callback for this activity.
+ * The callback will be triggered when a screen capture of this activity is attempted.
+ * This callback will be executed on the thread of the passed {@code executor}.
+ * For details, see {@link ScreenCaptureCallback#onScreenCaptured}.
+ */
+ @RequiresPermission(DETECT_SCREEN_CAPTURE)
+ public void registerScreenCaptureCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ScreenCaptureCallback callback) {
+ if (mScreenCaptureCallbackHandler == null) {
+ mScreenCaptureCallbackHandler = new ScreenCaptureCallbackHandler(mToken);
+ }
+ mScreenCaptureCallbackHandler.registerScreenCaptureCallback(executor, callback);
+ }
+
+
+ /**
+ * Unregisters a screen capture callback for this surface.
+ */
+ @RequiresPermission(DETECT_SCREEN_CAPTURE)
+ public void unregisterScreenCaptureCallback(@NonNull ScreenCaptureCallback callback) {
+ if (mScreenCaptureCallbackHandler != null) {
+ mScreenCaptureCallbackHandler.unregisterScreenCaptureCallback(callback);
+ }
+ }
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 461aa3c..e97e711 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -28,6 +28,7 @@
import android.app.IAssistDataReceiver;
import android.app.IInstrumentationWatcher;
import android.app.IProcessObserver;
+import android.app.IScreenCaptureObserver;
import android.app.IServiceConnection;
import android.app.IStopUserCallback;
import android.app.ITaskStackListener;
@@ -354,4 +355,23 @@
*/
android.window.BackNavigationInfo startBackNavigation(
in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor);
+
+ /**
+ * registers a callback to be invoked when the screen is captured.
+ *
+ * @param observer callback to be registered.
+ * @param activityToken The token for the activity to set the callback to.
+ * @hide
+ */
+ void registerScreenCaptureObserver(IBinder activityToken, IScreenCaptureObserver observer);
+
+ /**
+ * unregisters the screen capture callback which was registered with
+ * {@link #registerScreenCaptureObserver(ScreenCaptureObserver)}.
+ *
+ * @param observer callback to be unregistered.
+ * @param activityToken The token for the activity to unset the callback from.
+ * @hide
+ */
+ void unregisterScreenCaptureObserver(IBinder activityToken, IScreenCaptureObserver observer);
}
diff --git a/core/java/android/app/IScreenCaptureObserver.aidl b/core/java/android/app/IScreenCaptureObserver.aidl
new file mode 100644
index 0000000..1668e5d10
--- /dev/null
+++ b/core/java/android/app/IScreenCaptureObserver.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.app;
+
+/** {@hide} */
+interface IScreenCaptureObserver {
+ oneway void onScreenCaptured();
+}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 20869e0..6206f31 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -88,6 +88,7 @@
per-file *Task* = file:/services/core/java/com/android/server/wm/OWNERS
per-file Window* = file:/services/core/java/com/android/server/wm/OWNERS
per-file ConfigurationController.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
# TODO(b/174932174): determine the ownership of KeyguardManager.java
diff --git a/core/java/android/app/ScreenCaptureCallbackHandler.java b/core/java/android/app/ScreenCaptureCallbackHandler.java
new file mode 100644
index 0000000..997cf51
--- /dev/null
+++ b/core/java/android/app/ScreenCaptureCallbackHandler.java
@@ -0,0 +1,118 @@
+/*
+ * 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.app;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import java.util.concurrent.Executor;
+
+/** Handles screen capture callbacks.
+ * @hide
+ **/
+public class ScreenCaptureCallbackHandler {
+
+ private final IBinder mActivityToken;
+ private final ScreenCaptureObserver mObserver;
+ private final ArrayMap<Activity.ScreenCaptureCallback, ScreenCaptureRegistration>
+ mScreenCaptureRegistrations = new ArrayMap<>();
+
+ public ScreenCaptureCallbackHandler(@NonNull IBinder activityToken) {
+ mActivityToken = activityToken;
+ mObserver = new ScreenCaptureObserver(mScreenCaptureRegistrations);
+ }
+
+ private static class ScreenCaptureRegistration {
+ Executor mExecutor;
+ Activity.ScreenCaptureCallback mCallback;
+
+ ScreenCaptureRegistration(Executor executor, Activity.ScreenCaptureCallback callback) {
+ this.mExecutor = executor;
+ this.mCallback = callback;
+ }
+ }
+
+ private static class ScreenCaptureObserver extends IScreenCaptureObserver.Stub {
+ ArrayMap<Activity.ScreenCaptureCallback, ScreenCaptureRegistration> mRegistrations;
+
+ ScreenCaptureObserver(
+ ArrayMap<Activity.ScreenCaptureCallback, ScreenCaptureRegistration>
+ registrations) {
+ this.mRegistrations = registrations;
+ }
+
+ @Override
+ public void onScreenCaptured() {
+ for (ScreenCaptureRegistration registration : mRegistrations.values()) {
+ registration.mExecutor.execute(
+ () -> {
+ registration.mCallback.onScreenCaptured();
+ });
+ }
+ }
+ }
+
+ /**
+ * Start monitoring for screen captures of the activity, the callback will be triggered whenever
+ * a screen capture is attempted. This callback will be executed on the thread of the passed
+ * {@code executor}. If the window is FLAG_SECURE, the callback will not be triggered.
+ */
+ public void registerScreenCaptureCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Activity.ScreenCaptureCallback callback) {
+ ScreenCaptureRegistration registration =
+ new ScreenCaptureRegistration(executor, callback);
+ synchronized (mScreenCaptureRegistrations) {
+ if (mScreenCaptureRegistrations.containsKey(callback)) {
+ throw new IllegalStateException(
+ "Capture observer already registered with the activity");
+ }
+ mScreenCaptureRegistrations.put(callback, registration);
+ // register with system server only once.
+ if (mScreenCaptureRegistrations.size() == 1) {
+ try {
+ ActivityTaskManager.getService()
+ .registerScreenCaptureObserver(mActivityToken, mObserver);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ /** Stop monitoring for screen captures of the activity */
+ public void unregisterScreenCaptureCallback(@NonNull Activity.ScreenCaptureCallback callback) {
+ synchronized (mScreenCaptureRegistrations) {
+ if (!mScreenCaptureRegistrations.containsKey(callback)) {
+ throw new IllegalStateException(
+ "Capture observer not registered with the activity");
+ }
+ mScreenCaptureRegistrations.remove(callback);
+ // unregister only if no more registrations are left
+ if (mScreenCaptureRegistrations.size() == 0) {
+ try {
+ ActivityTaskManager.getService().unregisterScreenCaptureObserver(mActivityToken,
+ mObserver);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7e5523a..9843c8f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11049,6 +11049,7 @@
* @throws SecurityException if the caller is not a profile owner on an organization-owned
* managed profile.
* @throws IllegalStateException if called after the device setup has been completed.
+ * @throws UnsupportedOperationException if the api is not enabled.
* @see ManagedSubscriptionsPolicy
*/
public void setManagedSubscriptionsPolicy(@Nullable ManagedSubscriptionsPolicy policy) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 6ca708a..a408ea6 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -388,7 +388,7 @@
*
* <ul>
* <li>
- * The type has a 1 minute timeout.
+ * The type has a 3 minute timeout.
* A foreground service of this type must be stopped within the timeout by
* {@link android.app.Service#stopSelf),
* or {@link android.content.Context#stopService).
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 51662af..824d15c 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_SHAREABLE_WITH_PARENT =
+ "credentialShareableWithParent";
/** 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_SHAREABLE_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_SHAREABLE_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());
+ setMediaSharedWithParent(orig.isMediaSharedWithParent());
+ setCredentialShareableWithParent(orig.isCredentialShareableWithParent());
}
/**
@@ -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,47 @@
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 isMediaSharedWithParent() {
+ 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 mediaSharedWithParent");
+ }
+ /** @hide */
+ public void setMediaSharedWithParent(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 isCredentialShareableWithParent() {
+ if (isPresent(INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT)) {
+ return mCredentialShareableWithParent;
+ }
+ if (mDefaultProperties != null) return mDefaultProperties.mCredentialShareableWithParent;
+ throw new SecurityException(
+ "You don't have permission to query credentialShareableWithParent");
+ }
+ /** @hide */
+ public void setCredentialShareableWithParent(boolean val) {
+ this.mCredentialShareableWithParent = val;
+ setPresent(INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT);
+ }
+ private boolean mCredentialShareableWithParent;
+
/*
Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
OTA update between user-parent
@@ -550,6 +607,8 @@
+ getCrossProfileIntentFilterAccessControl()
+ ", mCrossProfileIntentResolutionStrategy="
+ getCrossProfileIntentResolutionStrategy()
+ + ", mMediaSharedWithParent=" + isMediaSharedWithParent()
+ + ", mCredentialShareableWithParent=" + isCredentialShareableWithParent()
+ "}";
}
@@ -572,6 +631,9 @@
+ getCrossProfileIntentFilterAccessControl());
pw.println(prefix + " mCrossProfileIntentResolutionStrategy="
+ getCrossProfileIntentResolutionStrategy());
+ pw.println(prefix + " mMediaSharedWithParent=" + isMediaSharedWithParent());
+ pw.println(prefix + " mCredentialShareableWithParent="
+ + isCredentialShareableWithParent());
}
/**
@@ -629,6 +691,12 @@
case ATTR_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY:
setCrossProfileIntentResolutionStrategy(parser.getAttributeInt(i));
break;
+ case ATTR_MEDIA_SHARED_WITH_PARENT:
+ setMediaSharedWithParent(parser.getAttributeBoolean(i));
+ break;
+ case ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT:
+ setCredentialShareableWithParent(parser.getAttributeBoolean(i));
+ break;
default:
Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
}
@@ -676,6 +744,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_SHAREABLE_WITH_PARENT)) {
+ serializer.attributeBoolean(null, ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT,
+ mCredentialShareableWithParent);
+ }
}
// For use only with an object that has already had any permission-lacking fields stripped out.
@@ -690,6 +766,8 @@
dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA);
dest.writeInt(mCrossProfileIntentFilterAccessControl);
dest.writeInt(mCrossProfileIntentResolutionStrategy);
+ dest.writeBoolean(mMediaSharedWithParent);
+ dest.writeBoolean(mCredentialShareableWithParent);
}
/**
@@ -708,6 +786,8 @@
mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean();
mCrossProfileIntentFilterAccessControl = source.readInt();
mCrossProfileIntentResolutionStrategy = source.readInt();
+ mMediaSharedWithParent = source.readBoolean();
+ mCredentialShareableWithParent = source.readBoolean();
}
@Override
@@ -743,6 +823,8 @@
CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_ALL;
private @CrossProfileIntentResolutionStrategy int mCrossProfileIntentResolutionStrategy =
CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT;
+ private boolean mMediaSharedWithParent = false;
+ private boolean mCredentialShareableWithParent = false;
public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
mShowInLauncher = showInLauncher;
@@ -794,6 +876,16 @@
return this;
}
+ public Builder setMediaSharedWithParent(boolean mediaSharedWithParent) {
+ mMediaSharedWithParent = mediaSharedWithParent;
+ return this;
+ }
+
+ public Builder setCredentialShareableWithParent(boolean credentialShareableWithParent) {
+ mCredentialShareableWithParent = credentialShareableWithParent;
+ return this;
+ }
+
/** Builds a UserProperties object with *all* values populated. */
public UserProperties build() {
return new UserProperties(
@@ -804,7 +896,9 @@
mUseParentsContacts,
mUpdateCrossProfileIntentFiltersOnOTA,
mCrossProfileIntentFilterAccessControl,
- mCrossProfileIntentResolutionStrategy);
+ mCrossProfileIntentResolutionStrategy,
+ mMediaSharedWithParent,
+ mCredentialShareableWithParent);
}
} // end Builder
@@ -816,7 +910,9 @@
@InheritDevicePolicy int inheritDevicePolicy,
boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA,
@CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl,
- @CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy) {
+ @CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy,
+ boolean mediaSharedWithParent,
+ boolean credentialShareableWithParent) {
mDefaultProperties = null;
setShowInLauncher(showInLauncher);
@@ -827,5 +923,7 @@
setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA);
setCrossProfileIntentFilterAccessControl(crossProfileIntentFilterAccessControl);
setCrossProfileIntentResolutionStrategy(crossProfileIntentResolutionStrategy);
+ setMediaSharedWithParent(mediaSharedWithParent);
+ setCredentialShareableWithParent(credentialShareableWithParent);
}
}
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index b4310f2..ec6a396 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -17,6 +17,7 @@
package android.credentials;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.credentials.CredentialEntry;
@@ -32,6 +33,7 @@
* Represents the type and contained data fields of a {@link Credential}.
* @hide
*/
+@TestApi
public final class CredentialDescription implements Parcelable {
/**
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index e15cec8..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;
@@ -329,6 +330,7 @@
*
* @hide
*/
+ @TestApi
public void registerCredentialDescription(
@NonNull RegisterCredentialDescriptionRequest request,
@Nullable CancellationSignal cancellationSignal,
@@ -376,6 +378,7 @@
*
* @hide
*/
+ @TestApi
public void unRegisterCredentialDescription(
@NonNull UnregisterCredentialDescriptionRequest request,
@Nullable CancellationSignal cancellationSignal,
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionException.java b/core/java/android/credentials/RegisterCredentialDescriptionException.java
index 3cf5a75..d19bb8e 100644
--- a/core/java/android/credentials/RegisterCredentialDescriptionException.java
+++ b/core/java/android/credentials/RegisterCredentialDescriptionException.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
@@ -32,6 +33,7 @@
*
* @hide
*/
+@TestApi
public class RegisterCredentialDescriptionException extends Exception {
@NonNull public final String errorType;
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionRequest.java b/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
index de31279..f257ac5 100644
--- a/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
+++ b/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
@@ -19,6 +19,7 @@
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;
@@ -36,6 +37,7 @@
*
* @hide
*/
+@TestApi
public final class RegisterCredentialDescriptionRequest implements Parcelable {
public static final String FLATTENED_REQUEST_STRING_KEY = "flattened_request_string";
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionException.java b/core/java/android/credentials/UnregisterCredentialDescriptionException.java
index 0c786bd..a16915a 100644
--- a/core/java/android/credentials/UnregisterCredentialDescriptionException.java
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionException.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
@@ -32,6 +33,7 @@
*
* @hide
*/
+@TestApi
public class UnregisterCredentialDescriptionException extends Exception {
@NonNull public final String errorType;
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
index f3454c1..6cf40e7 100644
--- a/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
@@ -19,6 +19,7 @@
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;
@@ -32,6 +33,7 @@
*
* @hide
*/
+@TestApi
public final class UnregisterCredentialDescriptionRequest implements Parcelable {
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index ff6e897..c9fc722 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -1246,7 +1246,7 @@
* between frames.</p>
*
* <p>The timestamps match the timestamps of the output surfaces with readout timestamp
- * enabled (via {@link OutputConfiguration#useReadoutTimestamp}) if:</p>
+ * enabled (via {@link OutputConfiguration#setReadoutTimestampEnabled}) if:</p>
* <ul>
* <li> Timestamp base is {@link OutputConfiguration#TIMESTAMP_BASE_DEFAULT} and the
* output
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 11b80cc..f20b25f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -4600,7 +4600,7 @@
* {@link CameraCaptureSession.CaptureCallback#onCaptureStarted }.</p>
* <p>In addition, the application can switch an output surface's timestamp from start of
* exposure to start of readout by calling
- * {@link android.hardware.camera2.params.OutputConfiguration#useReadoutTimestamp }.</p>
+ * {@link android.hardware.camera2.params.OutputConfiguration#setReadoutTimestampEnabled }.</p>
* <p>The readout timestamp is beneficial for video recording, because the encoder favors
* uniform timestamps, and the readout timestamps better reflect the cadence camera sensors
* output data.</p>
@@ -5688,4 +5688,5 @@
+
}
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3d83009..381c87d 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -4198,4 +4198,5 @@
+
}
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index dad7d3e..635e79c 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5699,4 +5699,5 @@
+
}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 8b7c5ec..857f62d 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -257,7 +257,7 @@
/**
* Timestamp is the start of readout in the same time domain as TIMESTAMP_BASE_SENSOR.
*
- * <p>NOTE: do not use! Use useReadoutTimestamp instead.</p>
+ * <p>NOTE: do not use! Use setReadoutTimestampEnabled instead.</p>
*
* @hide
*/
@@ -574,7 +574,7 @@
mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
mTimestampBase = TIMESTAMP_BASE_DEFAULT;
mMirrorMode = MIRROR_MODE_AUTO;
- mUseReadoutTimestamp = false;
+ mReadoutTimestampEnabled = false;
mIsReadoutSensorTimestampBase = false;
}
@@ -676,7 +676,7 @@
mDynamicRangeProfile = DynamicRangeProfiles.STANDARD;
mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
- mUseReadoutTimestamp = false;
+ mReadoutTimestampEnabled = false;
mIsReadoutSensorTimestampBase = false;
}
@@ -1050,7 +1050,7 @@
if (timestampBase == TIMESTAMP_BASE_READOUT_SENSOR) {
mTimestampBase = TIMESTAMP_BASE_SENSOR;
- mUseReadoutTimestamp = true;
+ mReadoutTimestampEnabled = true;
mIsReadoutSensorTimestampBase = true;
} else {
mTimestampBase = timestampBase;
@@ -1131,14 +1131,16 @@
* @param on The output image timestamp is the start of exposure time if false, and
* the start of readout time if true.
*/
- public void useReadoutTimestamp(boolean on) {
- mUseReadoutTimestamp = on;
+ public void setReadoutTimestampEnabled(boolean on) {
+ mReadoutTimestampEnabled = on;
}
/** Whether readout timestamp is used for this OutputConfiguration.
+ *
+ * @see #setReadoutTimestampEnabled
*/
- public boolean isReadoutTimestampUsed() {
- return mUseReadoutTimestamp;
+ public boolean isReadoutTimestampEnabled() {
+ return mReadoutTimestampEnabled;
}
/**
@@ -1172,7 +1174,7 @@
this.mStreamUseCase = other.mStreamUseCase;
this.mTimestampBase = other.mTimestampBase;
this.mMirrorMode = other.mMirrorMode;
- this.mUseReadoutTimestamp = other.mUseReadoutTimestamp;
+ this.mReadoutTimestampEnabled = other.mReadoutTimestampEnabled;
}
/**
@@ -1200,7 +1202,7 @@
int timestampBase = source.readInt();
int mirrorMode = source.readInt();
- boolean useReadoutTimestamp = source.readInt() == 1;
+ boolean readoutTimestampEnabled = source.readInt() == 1;
mSurfaceGroupId = surfaceSetId;
mRotation = rotation;
@@ -1229,7 +1231,7 @@
mStreamUseCase = streamUseCase;
mTimestampBase = timestampBase;
mMirrorMode = mirrorMode;
- mUseReadoutTimestamp = useReadoutTimestamp;
+ mReadoutTimestampEnabled = readoutTimestampEnabled;
}
/**
@@ -1350,7 +1352,7 @@
dest.writeLong(mStreamUseCase);
dest.writeInt(mTimestampBase);
dest.writeInt(mMirrorMode);
- dest.writeInt(mUseReadoutTimestamp ? 1 : 0);
+ dest.writeInt(mReadoutTimestampEnabled ? 1 : 0);
}
/**
@@ -1385,7 +1387,7 @@
mStreamUseCase != other.mStreamUseCase ||
mTimestampBase != other.mTimestampBase ||
mMirrorMode != other.mMirrorMode ||
- mUseReadoutTimestamp != other.mUseReadoutTimestamp)
+ mReadoutTimestampEnabled != other.mReadoutTimestampEnabled)
return false;
if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) {
return false;
@@ -1428,7 +1430,7 @@
mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
mDynamicRangeProfile, mColorSpace, mStreamUseCase,
- mTimestampBase, mMirrorMode, mUseReadoutTimestamp ? 1 : 0);
+ mTimestampBase, mMirrorMode, mReadoutTimestampEnabled ? 1 : 0);
}
return HashCodeHelpers.hashCode(
@@ -1438,7 +1440,7 @@
mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
mDynamicRangeProfile, mColorSpace, mStreamUseCase, mTimestampBase,
- mMirrorMode, mUseReadoutTimestamp ? 1 : 0);
+ mMirrorMode, mReadoutTimestampEnabled ? 1 : 0);
}
private static final String TAG = "OutputConfiguration";
@@ -1480,8 +1482,8 @@
private int mTimestampBase;
// Mirroring mode
private int mMirrorMode;
- // Use readout timestamp
- private boolean mUseReadoutTimestamp;
+ // readout timestamp
+ private boolean mReadoutTimestampEnabled;
// Whether the timestamp base is set to READOUT_SENSOR
private boolean mIsReadoutSensorTimestampBase;
}
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index 5bb1f03..5615418 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -15,14 +15,20 @@
*/
package android.hardware.hdmi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.IntRange;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
- * A class to encapsulate HDMI port information. Contains the capability of the ports such as
+ * Encapsulates HDMI port information. Contains the capability of the ports such as
* HDMI-CEC, MHL, ARC(Audio Return Channel), eARC and physical address assigned to each port.
*
* @hide
@@ -35,6 +41,18 @@
/** HDMI port type: Output */
public static final int PORT_OUTPUT = 1;
+ /**
+ * @hide
+ *
+ * @see HdmiPortInfo#getType()
+ */
+ @IntDef(prefix = { "PORT_" }, value = {
+ PORT_INPUT,
+ PORT_OUTPUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PortType {}
+
private final int mId;
private final int mType;
private final int mAddress;
@@ -46,43 +64,48 @@
/**
* Constructor.
*
- * @param id identifier assigned to each port. 1 for HDMI port 1
+ * @param id identifier assigned to each port. 1 for HDMI OUT port 1
* @param type HDMI port input/output type
* @param address physical address of the port
* @param cec {@code true} if HDMI-CEC is supported on the port
* @param mhl {@code true} if MHL is supported on the port
* @param arc {@code true} if audio return channel is supported on the port
- */
- public HdmiPortInfo(int id, int type, int address, boolean cec, boolean mhl, boolean arc) {
- this(id, type, address, cec, mhl, arc, false);
- }
-
- /**
- * Constructor.
*
- * @param id identifier assigned to each port. 1 for HDMI port 1
- * @param type HDMI port input/output type
- * @param address physical address of the port
- * @param cec {@code true} if HDMI-CEC is supported on the port
- * @param mhl {@code true} if MHL is supported on the port
- * @param arc {@code true} if audio return channel is supported on the port
- * @param earc {@code true} if eARC is supported on the port
+ * @deprecated use {@link Builder()} instead
*/
- public HdmiPortInfo(int id, int type, int address,
- boolean cec, boolean mhl, boolean arc, boolean earc) {
+ @Deprecated
+ public HdmiPortInfo(int id, @PortType int type,
+ @IntRange(from = 0) int address, boolean cec, boolean mhl, boolean arc) {
mId = id;
mType = type;
mAddress = address;
mCecSupported = cec;
mArcSupported = arc;
- mEarcSupported = earc;
+ mEarcSupported = false;
mMhlSupported = mhl;
}
/**
- * Returns the port id.
+ * Converts an instance to a builder
*
- * @return port id
+ * @hide
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ private HdmiPortInfo(Builder builder) {
+ this.mId = builder.mId;
+ this.mType = builder.mType;
+ this.mAddress = builder.mAddress;
+ this.mCecSupported = builder.mCecSupported;
+ this.mArcSupported = builder.mArcSupported;
+ this.mEarcSupported = builder.mEarcSupported;
+ this.mMhlSupported = builder.mMhlSupported;
+ }
+
+ /**
+ * Returns the port id.
*/
public int getId() {
return mId;
@@ -90,26 +113,22 @@
/**
* Returns the port type.
- *
- * @return port type
*/
+ @PortType
public int getType() {
return mType;
}
/**
* Returns the port address.
- *
- * @return port address
*/
+ @IntRange(from = 0)
public int getAddress() {
return mAddress;
}
/**
* Returns {@code true} if the port supports HDMI-CEC signaling.
- *
- * @return {@code true} if the port supports HDMI-CEC signaling.
*/
public boolean isCecSupported() {
return mCecSupported;
@@ -117,8 +136,6 @@
/**
* Returns {@code true} if the port supports MHL signaling.
- *
- * @return {@code true} if the port supports MHL signaling.
*/
public boolean isMhlSupported() {
return mMhlSupported;
@@ -126,8 +143,6 @@
/**
* Returns {@code true} if the port supports audio return channel.
- *
- * @return {@code true} if the port supports audio return channel
*/
public boolean isArcSupported() {
return mArcSupported;
@@ -135,8 +150,6 @@
/**
* Returns {@code true} if the port supports eARC.
- *
- * @return {@code true} if the port supports eARC.
*/
public boolean isEarcSupported() {
return mEarcSupported;
@@ -166,7 +179,12 @@
boolean arc = (source.readInt() == 1);
boolean mhl = (source.readInt() == 1);
boolean earc = (source.readInt() == 1);
- return new HdmiPortInfo(id, type, address, cec, mhl, arc, earc);
+ return new Builder(id, type, address)
+ .setCecSupported(cec)
+ .setArcSupported(arc)
+ .setEarcSupported(earc)
+ .setMhlSupported(mhl)
+ .build();
}
@Override
@@ -225,4 +243,95 @@
return java.util.Objects.hash(
mId, mType, mAddress, mCecSupported, mArcSupported, mMhlSupported, mEarcSupported);
}
+
+ /**
+ * Builder for {@link HdmiPortInfo} instances.
+ */
+ public static final class Builder {
+ // Required parameters
+ private int mId;
+ private int mType;
+ private int mAddress;
+
+ // Optional parameters that are set to false by default.
+ private boolean mCecSupported;
+ private boolean mArcSupported;
+ private boolean mEarcSupported;
+ private boolean mMhlSupported;
+
+ /**
+ * Constructor
+ *
+ * @param id identifier assigned to each port. 1 for HDMI OUT port 1
+ * @param type HDMI port input/output type
+ * @param address physical address of the port
+ * @throws IllegalArgumentException if the parameters are invalid
+ */
+ public Builder(int id, @PortType int type, @IntRange(from = 0) int address) {
+ if (type != PORT_INPUT && type != PORT_OUTPUT) {
+ throw new IllegalArgumentException(
+ "type should be " + PORT_INPUT + " or " + PORT_OUTPUT + ".");
+ }
+ if (address < 0) {
+ throw new IllegalArgumentException("address should be positive.");
+ }
+ mId = id;
+ mType = type;
+ mAddress = address;
+ }
+
+ private Builder(@NonNull HdmiPortInfo hdmiPortInfo) {
+ mId = hdmiPortInfo.mId;
+ mType = hdmiPortInfo.mType;
+ mAddress = hdmiPortInfo.mAddress;
+ mCecSupported = hdmiPortInfo.mCecSupported;
+ mArcSupported = hdmiPortInfo.mArcSupported;
+ mEarcSupported = hdmiPortInfo.mEarcSupported;
+ mMhlSupported = hdmiPortInfo.mMhlSupported;
+ }
+
+ /**
+ * Create a new {@link HdmiPortInfo} object.
+ */
+ @NonNull
+ public HdmiPortInfo build() {
+ return new HdmiPortInfo(this);
+ }
+
+ /**
+ * Sets the value for whether the port supports HDMI-CEC signaling.
+ */
+ @NonNull
+ public Builder setCecSupported(boolean supported) {
+ mCecSupported = supported;
+ return this;
+ }
+
+ /**
+ * Sets the value for whether the port supports audio return channel.
+ */
+ @NonNull
+ public Builder setArcSupported(boolean supported) {
+ mArcSupported = supported;
+ return this;
+ }
+
+ /**
+ * Sets the value for whether the port supports eARC.
+ */
+ @NonNull
+ public Builder setEarcSupported(boolean supported) {
+ mEarcSupported = supported;
+ return this;
+ }
+
+ /**
+ * Sets the value for whether the port supports MHL signaling.
+ */
+ @NonNull
+ public Builder setMhlSupported(boolean supported) {
+ mMhlSupported = supported;
+ return this;
+ }
+ }
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index dfd9054..f27c34c 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -175,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
diff --git a/core/java/android/hardware/soundtrigger/OWNERS b/core/java/android/hardware/soundtrigger/OWNERS
index e5d0370..01b2cb9 100644
--- a/core/java/android/hardware/soundtrigger/OWNERS
+++ b/core/java/android/hardware/soundtrigger/OWNERS
@@ -1,2 +1,2 @@
-ytai@google.com
+atneya@google.com
elaurent@google.com
diff --git a/core/java/android/hardware/usb/DisplayPortAltModeInfo.java b/core/java/android/hardware/usb/DisplayPortAltModeInfo.java
index febc643..ef2fefc 100644
--- a/core/java/android/hardware/usb/DisplayPortAltModeInfo.java
+++ b/core/java/android/hardware/usb/DisplayPortAltModeInfo.java
@@ -39,6 +39,8 @@
private final @DisplayPortAltModeStatus int mPartnerSinkStatus;
private final @DisplayPortAltModeStatus int mCableStatus;
private final int mNumLanes;
+ private final boolean mHpd;
+ private final int mLinkTrainingStatus;
/**
* Port Partners:
@@ -103,14 +105,18 @@
mPartnerSinkStatus = DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN;
mCableStatus = DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN;
mNumLanes = 0;
+ mHpd = false;
+ mLinkTrainingStatus = 0;
}
/** @hide */
public DisplayPortAltModeInfo(int partnerSinkStatus, int cableStatus,
- int numLanes) {
+ int numLanes, boolean hpd, int linkTrainingStatus) {
mPartnerSinkStatus = partnerSinkStatus;
mCableStatus = cableStatus;
mNumLanes = numLanes;
+ mHpd = hpd;
+ mLinkTrainingStatus = linkTrainingStatus;
}
/**
@@ -155,6 +161,8 @@
dest.writeInt(mPartnerSinkStatus);
dest.writeInt(mCableStatus);
dest.writeInt(mNumLanes);
+ dest.writeBoolean(mHpd);
+ dest.writeInt(mLinkTrainingStatus);
}
@NonNull
@@ -166,6 +174,10 @@
+ mCableStatus
+ " numLanes="
+ mNumLanes
+ + " hpd="
+ + mHpd
+ + " linkTrainingStatus="
+ + mLinkTrainingStatus
+ "}";
}
@@ -195,7 +207,10 @@
int partnerSinkStatus = in.readInt();
int cableStatus = in.readInt();
int numLanes = in.readInt();
- return new DisplayPortAltModeInfo(partnerSinkStatus, cableStatus, numLanes);
+ boolean hpd = in.readBoolean();
+ int linkTrainingStatus = in.readInt();
+ return new DisplayPortAltModeInfo(partnerSinkStatus, cableStatus, numLanes, hpd,
+ linkTrainingStatus);
}
@Override
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index ada5c55..62d9c69 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1006,12 +1006,15 @@
// been replaced with an implementation that will suspendAll and
// send VM_START.
System.out.println("Waiting for debugger first packet");
+
+ mWaiting = true;
while (!isDebuggerConnected()) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
}
+ mWaiting = false;
System.out.println("Debug.suspendAllAndSentVmStart");
VMDebug.suspendAllAndSendVmStart();
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/UserHandle.java b/core/java/android/os/UserHandle.java
index dca722e..4ce9184 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -123,6 +123,9 @@
@TestApi
public static final int MIN_SECONDARY_USER_ID = 10;
+ /** @hide */
+ public static final int MAX_SECONDARY_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
+
/**
* (Arbitrary) user handle cache size.
* {@link #CACHED_USER_HANDLES} caches user handles in the range of
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 9a25c70..d63d87d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3248,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,
@@ -5165,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#isMediaSharedWithParent()} 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)).isMediaSharedWithParent();
+ } catch (IllegalArgumentException e) {
+ // If the user doesn't exist, return false (for historical reasons)
+ return false;
}
}
@@ -5187,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#isCredentialShareableWithParent()} 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)).isCredentialShareableWithParent();
+ } catch (IllegalArgumentException e) {
+ // If the user doesn't exist, return false (for historical reasons)
+ return false;
}
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a72ccad..80dd488 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -18,17 +18,10 @@
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
-import static android.app.AppOpsManager.OP_READ_MEDIA_AUDIO;
import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES;
-import static android.app.AppOpsManager.OP_READ_MEDIA_VIDEO;
-import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
-import static android.app.AppOpsManager.OP_WRITE_MEDIA_AUDIO;
-import static android.app.AppOpsManager.OP_WRITE_MEDIA_IMAGES;
-import static android.app.AppOpsManager.OP_WRITE_MEDIA_VIDEO;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.PER_USER_RANGE;
@@ -1869,51 +1862,14 @@
// handle obscure cases like when an app targets Q but was installed on
// a device that was originally running on P before being upgraded to Q.
- /** {@hide} */
- public boolean checkPermissionReadAudio(boolean enforce,
- int pid, int uid, String packageName, @Nullable String featureId) {
- if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
- READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) {
- return false;
- }
- return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
- OP_READ_MEDIA_AUDIO);
- }
-
- /** {@hide} */
- public boolean checkPermissionWriteAudio(boolean enforce,
- int pid, int uid, String packageName, @Nullable String featureId) {
- if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
- WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) {
- return false;
- }
- return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
- OP_WRITE_MEDIA_AUDIO);
- }
-
- /** {@hide} */
- public boolean checkPermissionReadVideo(boolean enforce,
- int pid, int uid, String packageName, @Nullable String featureId) {
- if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
- READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) {
- return false;
- }
- return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
- OP_READ_MEDIA_VIDEO);
- }
-
- /** {@hide} */
- public boolean checkPermissionWriteVideo(boolean enforce,
- int pid, int uid, String packageName, @Nullable String featureId) {
- if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
- WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) {
- return false;
- }
- return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
- OP_WRITE_MEDIA_VIDEO);
- }
-
- /** {@hide} */
+ /**
+ * @deprecated This method should not be used since it check slegacy permissions,
+ * no longer valid. Clients should check the appropriate permissions directly
+ * instead (e.g. READ_MEDIA_IMAGES).
+ *
+ * {@hide}
+ */
+ @Deprecated
public boolean checkPermissionReadImages(boolean enforce,
int pid, int uid, String packageName, @Nullable String featureId) {
if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
@@ -1924,17 +1880,6 @@
OP_READ_MEDIA_IMAGES);
}
- /** {@hide} */
- public boolean checkPermissionWriteImages(boolean enforce,
- int pid, int uid, String packageName, @Nullable String featureId) {
- if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
- WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) {
- return false;
- }
- return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
- OP_WRITE_MEDIA_IMAGES);
- }
-
private boolean checkExternalStoragePermissionAndAppOp(boolean enforce,
int pid, int uid, String packageName, @Nullable String featureId, String permission,
int op) {
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 5b05f21..9de424f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9738,7 +9738,7 @@
/**
* Indicates whether "seen" notifications should be suppressed from the lockscreen.
* <p>
- * Type: int (0 for false, 1 for true)
+ * Type: int (0 for unset, 1 for true, 2 for false)
*
* @hide
*/
@@ -14568,15 +14568,6 @@
"app_auto_restriction_enabled";
/**
- * Feature flag to enable or disable the Forced App Standby feature.
- * Type: int (0 for false, 1 for true)
- * Default: 1
- * @hide
- */
- @Readable
- public static final String FORCED_APP_STANDBY_ENABLED = "forced_app_standby_enabled";
-
- /**
* Whether or not to enable Forced App Standby on small battery devices.
* Type: int (0 for false, 1 for true)
* Default: 0
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 70b04a1..06fe523 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -166,6 +166,13 @@
public static final String SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS =
"settings_show_udfps_enroll_in_settings";
+ /**
+ * Flag to enable lock screen credentials transfer API in Android U.
+ * @hide
+ */
+ public static final String SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API =
+ "settings_enable_lockscreen_transfer_api";
+
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -207,6 +214,7 @@
DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
DEFAULT_FLAGS.put(SETTINGS_FLASH_ALERTS, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "false");
+ DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 9fdda29..4f3c5fe 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -22,6 +22,7 @@
import com.android.internal.policy.IShortcutService;
import android.app.IAssistDataReceiver;
+import android.content.ComponentName;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -1000,4 +1001,14 @@
* Mark a SurfaceSyncGroup stored in WindowManager as ready.
*/
oneway void markSurfaceSyncGroupReady(in IBinder syncGroupToken);
+
+ /**
+ * Invoked when a screenshot is taken of the default display to notify registered listeners.
+ *
+ * Should be invoked only by SysUI.
+ *
+ * @param displayId id of the display screenshot.
+ * @return List of ComponentNames corresponding to the activities that were notified.
+ */
+ List<ComponentName> notifyScreenshotListeners(int displayId);
}
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/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 5dccd06..5165478 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2334,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) {
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/WindowManager.java b/core/java/android/view/WindowManager.java
index e437c1f..5f6f55a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -94,6 +94,7 @@
import android.app.Presentation;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -5269,4 +5270,18 @@
default Bitmap snapshotTaskForRecents(@IntRange(from = 0) int taskId) {
return null;
}
+
+ /**
+ * Invoked when a screenshot is taken of the default display to notify registered listeners.
+ *
+ * Should be invoked only by SysUI.
+ *
+ * @param displayId id of the display screenshot.
+ * @return List of ComponentNames corresponding to the activities that were notified.
+ * @hide
+ */
+ @SystemApi
+ default @NonNull List<ComponentName> notifyScreenshotListeners(int displayId) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 19d49d9..7ea8c0c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.annotation.UiContext;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Region;
@@ -412,4 +413,15 @@
IBinder getDefaultToken() {
return mDefaultToken;
}
+
+ @Override
+ @NonNull
+ public List<ComponentName> notifyScreenshotListeners(int displayId) {
+ try {
+ return List.copyOf(WindowManagerGlobal.getWindowManagerService()
+ .notifyScreenshotListeners(displayId));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 141849f..b74b80e 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -20,6 +20,8 @@
import android.graphics.Point;
import android.graphics.Rect;
+import java.util.function.Supplier;
+
/**
* Metrics about a Window, consisting of the bounds and {@link WindowInsets}.
* <p>
@@ -50,8 +52,9 @@
public final class WindowMetrics {
@NonNull
private final Rect mBounds;
- @NonNull
- private final WindowInsets mWindowInsets;
+
+ private WindowInsets mWindowInsets;
+ private Supplier<WindowInsets> mWindowInsetsSupplier;
/** @see android.util.DisplayMetrics#density */
private final float mDensity;
@@ -81,6 +84,21 @@
}
/**
+ * Similar to {@link #WindowMetrics(Rect, WindowInsets, float)} but the window insets are
+ * computed when {@link #getWindowInsets()} is first time called. This reduces unnecessary
+ * calculation and the overhead of obtaining insets state from server side because most
+ * callers are usually only interested in {@link #getBounds()}.
+ *
+ * @hide
+ */
+ public WindowMetrics(@NonNull Rect bounds, @NonNull Supplier<WindowInsets> windowInsetsSupplier,
+ float density) {
+ mBounds = bounds;
+ mWindowInsetsSupplier = windowInsetsSupplier;
+ mDensity = density;
+ }
+
+ /**
* Returns the bounds of the area associated with this window or
* {@link android.annotation.UiContext}.
* <p>
@@ -121,7 +139,10 @@
*/
@NonNull
public WindowInsets getWindowInsets() {
- return mWindowInsets;
+ if (mWindowInsets != null) {
+ return mWindowInsets;
+ }
+ return mWindowInsets = mWindowInsetsSupplier.get();
}
/**
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 06449d5..11bd47d 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -41,6 +41,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Supplier;
/**
* A controller to handle {@link android.view.WindowMetrics} related APIs, which are
@@ -53,6 +54,9 @@
* @hide
*/
public final class WindowMetricsController {
+ // TODO(b/151908239): Remove and always enable this if it is stable.
+ private static final boolean LAZY_WINDOW_INSETS = android.os.SystemProperties.getBoolean(
+ "persist.wm.debug.win_metrics_lazy_insets", false);
private final Context mContext;
public WindowMetricsController(@NonNull Context context) {
@@ -92,16 +96,11 @@
windowingMode = winConfig.getWindowingMode();
}
final IBinder token = Context.getToken(mContext);
- final WindowInsets windowInsets = getWindowInsetsFromServerForCurrentDisplay(token,
- bounds, isScreenRound, windowingMode);
- return new WindowMetrics(bounds, windowInsets, density);
- }
-
- private WindowInsets getWindowInsetsFromServerForCurrentDisplay(
- IBinder token, Rect bounds, boolean isScreenRound,
- @WindowConfiguration.WindowingMode int windowingMode) {
- return getWindowInsetsFromServerForDisplay(mContext.getDisplayId(), token, bounds,
- isScreenRound, windowingMode);
+ final Supplier<WindowInsets> insetsSupplier = () -> getWindowInsetsFromServerForDisplay(
+ mContext.getDisplayId(), token, bounds, isScreenRound, windowingMode);
+ return LAZY_WINDOW_INSETS
+ ? new WindowMetrics(new Rect(bounds), insetsSupplier, density)
+ : new WindowMetrics(new Rect(bounds), insetsSupplier.get(), density);
}
/**
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
index 7571073..cc37c69 100644
--- a/core/java/com/android/internal/expresslog/Counter.java
+++ b/core/java/com/android/internal/expresslog/Counter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -39,9 +39,7 @@
* @hide
*/
public static void logIncrement(@NonNull String metricId, long amount) {
- final long metricIdHash = hashString(metricId);
+ final long metricIdHash = Utils.hashString(metricId);
FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
}
-
- private static native long hashString(String stringToHash);
}
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/core/java/com/android/internal/expresslog/Utils.java
new file mode 100644
index 0000000..d82192f
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/Utils.java
@@ -0,0 +1,21 @@
+/*
+ * 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.expresslog;
+
+final class Utils {
+ static native long hashString(String stringToHash);
+}
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/jni/Android.bp b/core/jni/Android.bp
index 91d2ecb..eca85ff 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -219,7 +219,7 @@
"android_security_Scrypt.cpp",
"com_android_internal_content_om_OverlayConfig.cpp",
"com_android_internal_content_om_OverlayManagerImpl.cpp",
- "com_android_internal_expresslog_Counter.cpp",
+ "com_android_internal_expresslog_Utils.cpp",
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 578cf24..b550f28 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -200,7 +200,7 @@
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
-extern int register_com_android_internal_expresslog_Counter(JNIEnv* env);
+extern int register_com_android_internal_expresslog_Utils(JNIEnv* env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1586,7 +1586,7 @@
REG_JNI(register_android_os_incremental_IncrementalManager),
REG_JNI(register_com_android_internal_content_om_OverlayConfig),
REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
- REG_JNI(register_com_android_internal_expresslog_Counter),
+ REG_JNI(register_com_android_internal_expresslog_Utils),
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/com_android_internal_expresslog_Counter.cpp b/core/jni/com_android_internal_expresslog_Utils.cpp
similarity index 83%
rename from core/jni/com_android_internal_expresslog_Counter.cpp
rename to core/jni/com_android_internal_expresslog_Utils.cpp
index d4a8c23..d33a7bd 100644
--- a/core/jni/com_android_internal_expresslog_Counter.cpp
+++ b/core/jni/com_android_internal_expresslog_Utils.cpp
@@ -26,7 +26,7 @@
static jclass g_stringClass = nullptr;
/**
- * Class: com_android_internal_expresslog_Counter
+ * Class: com_android_internal_expresslog_Utils
* Method: hashString
* Signature: (Ljava/lang/String;)J
*/
@@ -43,15 +43,15 @@
{"hashString", "(Ljava/lang/String;)J", (void*)hashString},
};
-static const char* const kCounterPathName = "com/android/internal/expresslog/Counter";
+static const char* const kUtilsPathName = "com/android/internal/expresslog/Utils";
namespace android {
-int register_com_android_internal_expresslog_Counter(JNIEnv* env) {
+int register_com_android_internal_expresslog_Utils(JNIEnv* env) {
jclass stringClass = FindClassOrDie(env, "java/lang/String");
g_stringClass = MakeGlobalRefOrDie(env, stringClass);
- return RegisterMethodsOrDie(env, kCounterPathName, g_methods, NELEM(g_methods));
+ return RegisterMethodsOrDie(env, kUtilsPathName, g_methods, NELEM(g_methods));
}
} // namespace android
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
index 64df348..e82b699 100755
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
@@ -41,34 +41,58 @@
new EqualsTester()
.addEqualityGroup(
- new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
- isEarcSupported),
- new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
- isEarcSupported))
+ new HdmiPortInfo.Builder(portId, portType, address)
+ .setCecSupported(isCec)
+ .setMhlSupported(isMhl)
+ .setArcSupported(isArcSupported)
+ .setEarcSupported(isEarcSupported),
+ new HdmiPortInfo.Builder(portId, portType, address)
+ .setCecSupported(isCec)
+ .setMhlSupported(isMhl)
+ .setArcSupported(isArcSupported)
+ .setEarcSupported(isEarcSupported))
.addEqualityGroup(
- new HdmiPortInfo(
- portId + 1, portType, address, isCec, isMhl, isArcSupported,
- isEarcSupported))
+ new HdmiPortInfo.Builder(portId + 1, portType, address)
+ .setCecSupported(isCec)
+ .setMhlSupported(isMhl)
+ .setArcSupported(isArcSupported)
+ .setEarcSupported(isEarcSupported))
.addEqualityGroup(
- new HdmiPortInfo(
- portId, portType + 1, address, isCec, isMhl, isArcSupported,
- isEarcSupported))
+ new HdmiPortInfo.Builder(portId, portType + 1, address)
+ .setCecSupported(isCec)
+ .setMhlSupported(isMhl)
+ .setArcSupported(isArcSupported)
+ .setEarcSupported(isEarcSupported))
.addEqualityGroup(
- new HdmiPortInfo(
- portId, portType, address + 1, isCec, isMhl, isArcSupported,
- isEarcSupported))
+ new HdmiPortInfo.Builder(portId, portType, address + 1)
+ .setCecSupported(isCec)
+ .setMhlSupported(isMhl)
+ .setArcSupported(isArcSupported)
+ .setEarcSupported(isEarcSupported))
.addEqualityGroup(
- new HdmiPortInfo(portId, portType, address, !isCec, isMhl, isArcSupported,
- isEarcSupported))
+ new HdmiPortInfo.Builder(portId, portType, address)
+ .setCecSupported(!isCec)
+ .setMhlSupported(isMhl)
+ .setArcSupported(isArcSupported)
+ .setEarcSupported(isEarcSupported))
.addEqualityGroup(
- new HdmiPortInfo(portId, portType, address, isCec, !isMhl, isArcSupported,
- isEarcSupported))
+ new HdmiPortInfo.Builder(portId, portType, address)
+ .setCecSupported(isCec)
+ .setMhlSupported(!isMhl)
+ .setArcSupported(isArcSupported)
+ .setEarcSupported(isEarcSupported))
.addEqualityGroup(
- new HdmiPortInfo(portId, portType, address, isCec, isMhl, !isArcSupported,
- isEarcSupported))
+ new HdmiPortInfo.Builder(portId, portType, address)
+ .setCecSupported(isCec)
+ .setMhlSupported(isMhl)
+ .setArcSupported(!isArcSupported)
+ .setEarcSupported(isEarcSupported))
.addEqualityGroup(
- new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
- !isEarcSupported))
+ new HdmiPortInfo.Builder(portId, portType, address)
+ .setCecSupported(isCec)
+ .setMhlSupported(isMhl)
+ .setArcSupported(isArcSupported)
+ .setEarcSupported(!isEarcSupported))
.testEquals();
}
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index bf7f3bd..a37cb80 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -310,6 +310,7 @@
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<!-- Permission required for UiModeManager CTS test -->
<permission name="android.permission.READ_PROJECTION_STATE"/>
+ <permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
<permission name="android.permission.READ_WIFI_CREDENTIAL"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
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/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml
new file mode 100644
index 0000000..a3ca74f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_900" android:alpha="0.6" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml
new file mode 100644
index 0000000..a3ca74f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_900" android:alpha="0.6" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml
new file mode 100644
index 0000000..60f3cfe
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+ <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
new file mode 100644
index 0000000..ef97ea1
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/letterbox_restart_button_background_ripple">
+ <item android:drawable="@drawable/letterbox_restart_button_background"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml
new file mode 100644
index 0000000..c247c6e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:state_checked="true"
+ android:drawable="@drawable/letterbox_restart_checkbox_checked" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/letterbox_restart_checkbox_checked" />
+ <item android:state_pressed="false"
+ android:drawable="@drawable/letterbox_restart_checkbox_unchecked" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml
new file mode 100644
index 0000000..4f97e2c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20"
+ android:tint="?android:attr/textColorSecondary">
+ <group
+ android:scaleX="0.83333333333"
+ android:scaleY="0.83333333333"
+ android:translateX="0"
+ android:translateY="0">
+ <path
+ android:fillColor="?android:attr/textColorSecondary"
+ android:pathData="M10.6,16.2 L17.65,9.15 16.25,7.75 10.6,13.4 7.75,10.55 6.35,11.95ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21Z"/>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml
new file mode 100644
index 0000000..bb14d19
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20"
+ android:tint="?android:attr/textColorSecondary">
+ <group
+ android:scaleX="0.83333333333"
+ android:scaleY="0.83333333333"
+ android:translateX="0"
+ android:translateY="0">
+ <path
+ android:fillColor="?android:attr/textColorSecondary"
+ android:pathData="M5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V5Q19,5 19,5Q19,5 19,5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19Z"/>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
new file mode 100644
index 0000000..e3c18a2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <corners android:radius="@dimen/letterbox_restart_dialog_corner_radius"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml
new file mode 100644
index 0000000..af89d41
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" android:width="1dp"/>
+ <solid android:color="?androidprv:attr/colorSurface" />
+ <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
new file mode 100644
index 0000000..e32aefc
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/letterbox_restart_dismiss_button_background_ripple">
+ <item android:drawable="@drawable/letterbox_restart_dismiss_button_background"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
new file mode 100644
index 0000000..5053971
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="@dimen/letterbox_restart_dialog_title_icon_width"
+ android:height="@dimen/letterbox_restart_dialog_title_icon_height"
+ android:viewportWidth="45"
+ android:viewportHeight="44">
+ <group
+ android:scaleX="0.8"
+ android:scaleY="0.8"
+ android:translateX="8"
+ android:translateY="8">
+ <path
+ android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+ android:fillColor="?androidprv:attr/colorAccentPrimaryVariant"/>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml
new file mode 100644
index 0000000..b6e0172
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/letterbox_restart_dialog_title_icon_width"
+ android:height="@dimen/letterbox_restart_dialog_title_icon_height"
+ android:viewportWidth="45"
+ android:viewportHeight="44">
+ <group
+ android:scaleX="0.8"
+ android:scaleY="0.8"
+ android:translateX="8"
+ android:translateY="8">
+ <path
+ android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+ android:fillColor="@color/compat_controls_text"/>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
new file mode 100644
index 0000000..ba9852c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
@@ -0,0 +1,142 @@
+<!--
+ ~ 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.
+ -->
+<com.android.wm.shell.compatui.RestartDialogLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/system_neutral1_900">
+
+ <!-- The background of the top-level layout acts as the background dim. -->
+
+ <!--TODO (b/266288912): Resolve overdraw warning -->
+
+ <!-- Vertical margin will be set dynamically since it depends on task bounds.
+ Setting the alpha of the dialog container to 0, since it shouldn't be visible until the
+ enter animation starts. -->
+ <FrameLayout
+ android:id="@+id/letterbox_restart_dialog_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/letterbox_restart_dialog_margin"
+ android:background="@drawable/letterbox_restart_dialog_background"
+ android:alpha="0"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="@dimen/letterbox_restart_dialog_width">
+
+ <!-- The ScrollView should only wrap the content of the dialog, otherwise the background
+ corner radius will be cut off when scrolling to the top/bottom. -->
+
+ <ScrollView android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:padding="24dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:importantForAccessibility="no"
+ android:layout_width="@dimen/letterbox_restart_dialog_title_icon_width"
+ android:layout_height="@dimen/letterbox_restart_dialog_title_icon_height"
+ android:src="@drawable/letterbox_restart_header_ic_arrows"/>
+
+ <TextView
+ android:layout_marginVertical="16dp"
+ android:id="@+id/letterbox_restart_dialog_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/letterbox_restart_dialog_title"
+ android:textAlignment="center"
+ android:textAppearance="@style/RestartDialogTitleText"/>
+
+ <TextView
+ android:textAppearance="@style/RestartDialogBodyText"
+ android:id="@+id/letterbox_restart_dialog_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/letterbox_restart_dialog_description"
+ android:textAlignment="center"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="center_vertical"
+ android:layout_marginVertical="32dp">
+
+ <CheckBox
+ android:id="@+id/letterbox_restart_dialog_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:button="@drawable/letterbox_restart_checkbox_button"/>
+
+ <TextView
+ android:textAppearance="@style/RestartDialogCheckboxText"
+ android:layout_marginStart="12dp"
+ android:id="@+id/letterbox_restart_dialog_checkbox_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/letterbox_restart_dialog_checkbox_title"
+ android:textAlignment="textStart"/>
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:minHeight="@dimen/letterbox_restart_dialog_button_height"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:textAppearance="@style/RestartDialogDismissButton"
+ android:id="@+id/letterbox_restart_dialog_dismiss_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/letterbox_restart_dialog_button_width"
+ android:minHeight="@dimen/letterbox_restart_dialog_button_height"
+ android:layout_gravity="start"
+ android:background=
+ "@drawable/letterbox_restart_dismiss_button_background_ripple"
+ android:text="@string/letterbox_restart_cancel"
+ android:contentDescription="@string/letterbox_restart_cancel"/>
+
+ <Button
+ android:textAppearance="@style/RestartDialogConfirmButton"
+ android:id="@+id/letterbox_restart_dialog_restart_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/letterbox_restart_dialog_button_width"
+ android:minHeight="@dimen/letterbox_restart_dialog_button_height"
+ android:layout_gravity="end"
+ android:background=
+ "@drawable/letterbox_restart_button_background_ripple"
+ android:text="@string/letterbox_restart_restart"
+ android:contentDescription="@string/letterbox_restart_restart"/>
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ </ScrollView>
+
+ </FrameLayout>
+
+</com.android.wm.shell.compatui.RestartDialogLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 3ee20ea..a1da649 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -273,6 +273,39 @@
<!-- The space between two actions in the letterbox education dialog -->
<dimen name="letterbox_education_dialog_space_between_actions">24dp</dimen>
+ <!-- The margin between the dialog container and its parent. -->
+ <dimen name="letterbox_restart_dialog_margin">24dp</dimen>
+
+ <!-- The corner radius of the restart confirmation dialog. -->
+ <dimen name="letterbox_restart_dialog_corner_radius">28dp</dimen>
+
+ <!-- The fixed width of the dialog if there is enough space in the parent. -->
+ <dimen name="letterbox_restart_dialog_width">348dp</dimen>
+
+ <!-- The width of the top icon in the restart confirmation dialog. -->
+ <dimen name="letterbox_restart_dialog_title_icon_width">32dp</dimen>
+
+ <!-- The height of the top icon in the restart confirmation dialog. -->
+ <dimen name="letterbox_restart_dialog_title_icon_height">32dp</dimen>
+
+ <!-- The width of an icon in the restart confirmation dialog. -->
+ <dimen name="letterbox_restart_dialog_icon_width">40dp</dimen>
+
+ <!-- The height of an icon in the restart confirmation dialog. -->
+ <dimen name="letterbox_restart_dialog_icon_height">32dp</dimen>
+
+ <!-- The space between two actions in the restart confirmation dialog -->
+ <dimen name="letterbox_restart_dialog_space_between_actions">24dp</dimen>
+
+ <!-- The width of the buttons in the restart dialog -->
+ <dimen name="letterbox_restart_dialog_button_width">82dp</dimen>
+
+ <!-- The width of the buttons in the restart dialog -->
+ <dimen name="letterbox_restart_dialog_button_height">36dp</dimen>
+
+ <!-- The corner radius of the buttons in the restart dialog -->
+ <dimen name="letterbox_restart_dialog_button_radius">18dp</dimen>
+
<!-- The width of the brand image on staring surface. -->
<dimen name="starting_surface_brand_image_width">200dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 9490ddc..3fd97ef 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -193,6 +193,23 @@
<!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
<string name="letterbox_education_expand_button_description">Expand for more information.</string>
+ <!-- The title of the restart confirmation dialog. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_restart_dialog_title">Restart for a better view?</string>
+
+ <!-- The description of the restart confirmation dialog. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_restart_dialog_description">You can restart the app so it looks better on
+ your screen, but you may lose your progress or any unsaved changes
+ </string>
+
+ <!-- Button text for dismissing the restart confirmation dialog. [CHAR LIMIT=20] -->
+ <string name="letterbox_restart_cancel">Cancel</string>
+
+ <!-- Button text for dismissing the restart confirmation dialog. [CHAR LIMIT=20] -->
+ <string name="letterbox_restart_restart">Restart</string>
+
+ <!-- Checkbox text for asking to not show the restart confirmation dialog again. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_restart_dialog_checkbox_title">Don\u2019t show again</string>
+
<!-- Freeform window caption strings -->
<!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
<string name="maximize_button_text">Maximize</string>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index a859721..e8f340c 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -63,4 +63,42 @@
<item name="android:lineHeight">16sp</item>
<item name="android:textColor">@color/tv_pip_edu_text</item>
</style>
+
+ <style name="RestartDialogTitleText">
+ <item name="android:textSize">24sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:lineSpacingExtra">2sp</item>
+ <item name="android:textAppearance">
+ @*android:style/TextAppearance.DeviceDefault.Headline
+ </item>
+ </style>
+
+ <style name="RestartDialogBodyText">
+ <item name="android:textSize">14sp</item>
+ <item name="android:letterSpacing">0.02</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:lineSpacingExtra">2sp</item>
+ <item name="android:textAppearance">
+ @*android:style/TextAppearance.DeviceDefault.Body2
+ </item>
+ </style>
+
+ <style name="RestartDialogCheckboxText">
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:lineSpacingExtra">4sp</item>
+ <item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault</item>
+ </style>
+
+ <style name="RestartDialogDismissButton">
+ <item name="android:lineSpacingExtra">2sp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="RestartDialogConfirmButton">
+ <item name="android:lineSpacingExtra">2sp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ </style>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 4f33a71..e326e39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -16,11 +16,12 @@
package com.android.wm.shell.compatui;
+import android.annotation.NonNull;
+import android.app.TaskInfo;
import android.content.Context;
+import android.content.SharedPreferences;
import android.provider.DeviceConfig;
-import androidx.annotation.NonNull;
-
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;
@@ -34,11 +35,23 @@
@WMSingleton
public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener {
- static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog";
+ private static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG =
+ "enable_letterbox_restart_dialog";
- static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
+ private static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
"enable_letterbox_reachability_education";
+ /**
+ * The name of the {@link SharedPreferences} that holds which user has seen the Restart
+ * confirmation dialog.
+ */
+ private static final String DONT_SHOW_RESTART_DIALOG_PREF_NAME = "dont_show_restart_dialog";
+
+ /**
+ * The {@link SharedPreferences} instance for {@link #DONT_SHOW_RESTART_DIALOG_PREF_NAME}.
+ */
+ private final SharedPreferences mSharedPreferences;
+
// Whether the extended restart dialog is enabled
private boolean mIsRestartDialogEnabled;
@@ -70,6 +83,8 @@
false);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
this);
+ mSharedPreferences = context.getSharedPreferences(DONT_SHOW_RESTART_DIALOG_PREF_NAME,
+ Context.MODE_PRIVATE);
}
/**
@@ -102,6 +117,20 @@
mIsReachabilityEducationOverrideEnabled = enabled;
}
+ boolean getDontShowRestartDialogAgain(TaskInfo taskInfo) {
+ final int userId = taskInfo.userId;
+ final String packageName = taskInfo.topActivity.getPackageName();
+ return mSharedPreferences.getBoolean(
+ getDontShowAgainRestartKey(userId, packageName), /* default= */ false);
+ }
+
+ void setDontShowRestartDialogAgain(TaskInfo taskInfo) {
+ final int userId = taskInfo.userId;
+ final String packageName = taskInfo.topActivity.getPackageName();
+ mSharedPreferences.edit().putBoolean(getDontShowAgainRestartKey(userId, packageName),
+ true).apply();
+ }
+
@Override
public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
// TODO(b/263349751): Update flag and default value to true
@@ -116,4 +145,8 @@
KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false);
}
}
-}
+
+ private String getDontShowAgainRestartKey(int userId, String packageName) {
+ return packageName + "@" + userId;
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 6627de5..3b2db51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -24,6 +24,7 @@
import android.hardware.display.DisplayManager;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.view.Display;
import android.view.InsetsSourceControl;
@@ -49,6 +50,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -91,6 +93,18 @@
private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0);
/**
+ * {@link SparseArray} that maps task ids to {@link RestartDialogWindowManager} that are
+ * currently visible
+ */
+ private final SparseArray<RestartDialogWindowManager> mTaskIdToRestartDialogWindowManagerMap =
+ new SparseArray<>(0);
+
+ /**
+ * {@link Set} of task ids for which we need to display a restart confirmation dialog
+ */
+ private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>();
+
+ /**
* The active Letterbox Education layout if there is one (there can be at most one active).
*
* <p>An active layout is a layout that is eligible to be shown for the associated task but
@@ -111,12 +125,15 @@
private final ShellExecutor mMainExecutor;
private final Lazy<Transitions> mTransitionsLazy;
private final DockStateReader mDockStateReader;
+ private final CompatUIConfiguration mCompatUIConfiguration;
private CompatUICallback mCallback;
// Only show each hint once automatically in the process life.
private final CompatUIHintsState mCompatUIHintsState;
+ private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
+
// Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
// be shown.
private boolean mKeyguardShowing;
@@ -130,7 +147,9 @@
SyncTransactionQueue syncQueue,
ShellExecutor mainExecutor,
Lazy<Transitions> transitionsLazy,
- DockStateReader dockStateReader) {
+ DockStateReader dockStateReader,
+ CompatUIConfiguration compatUIConfiguration,
+ CompatUIShellCommandHandler compatUIShellCommandHandler) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -140,14 +159,17 @@
mMainExecutor = mainExecutor;
mTransitionsLazy = transitionsLazy;
mCompatUIHintsState = new CompatUIHintsState();
- shellInit.addInitCallback(this::onInit, this);
mDockStateReader = dockStateReader;
+ mCompatUIConfiguration = compatUIConfiguration;
+ mCompatUIShellCommandHandler = compatUIShellCommandHandler;
+ shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellController.addKeyguardChangeListener(this);
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
+ mCompatUIShellCommandHandler.onInit();
}
/** Sets the callback for UI interactions. */
@@ -164,6 +186,9 @@
*/
public void onCompatInfoChanged(TaskInfo taskInfo,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
+ mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
+ }
if (taskInfo.configuration == null || taskListener == null) {
// Null token means the current foreground activity is not in compatibility mode.
removeLayouts(taskInfo.taskId);
@@ -172,6 +197,7 @@
createOrUpdateCompatLayout(taskInfo, taskListener);
createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
+ createOrUpdateRestartDialogLayout(taskInfo, taskListener);
}
@Override
@@ -278,7 +304,21 @@
ShellTaskOrganizer.TaskListener taskListener) {
return new CompatUIWindowManager(context,
taskInfo, mSyncQueue, mCallback, taskListener,
- mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState);
+ mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
+ mCompatUIConfiguration, this::onRestartButtonClicked);
+ }
+
+ private void onRestartButtonClicked(
+ Pair<TaskInfo, ShellTaskOrganizer.TaskListener> taskInfoState) {
+ if (mCompatUIConfiguration.isRestartDialogEnabled()
+ && !mCompatUIConfiguration.getDontShowRestartDialogAgain(
+ taskInfoState.first)) {
+ // We need to show the dialog
+ mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
+ onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
+ } else {
+ mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+ }
}
private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
@@ -327,6 +367,60 @@
mActiveLetterboxEduLayout = null;
}
+ private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ RestartDialogWindowManager layout =
+ mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
+ if (layout != null) {
+ // TODO(b/266262111) Handle theme change when taskListener changes
+ if (layout.getTaskListener() != taskListener) {
+ mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
+ }
+ layout.setRequestRestartDialog(
+ mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId));
+ // UI already exists, update the UI layout.
+ if (!layout.updateCompatInfo(taskInfo, taskListener,
+ showOnDisplay(layout.getDisplayId()))) {
+ // The layout is no longer eligible to be shown, remove from active layouts.
+ mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
+ }
+ return;
+ }
+ // Create a new UI layout.
+ final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+ if (context == null) {
+ return;
+ }
+ layout = createRestartDialogWindowManager(context, taskInfo, taskListener);
+ layout.setRequestRestartDialog(
+ mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId));
+ if (layout.createLayout(showOnDisplay(taskInfo.displayId))) {
+ // The new layout is eligible to be shown, add it the active layouts.
+ mTaskIdToRestartDialogWindowManagerMap.put(taskInfo.taskId, layout);
+ }
+ }
+
+ @VisibleForTesting
+ RestartDialogWindowManager createRestartDialogWindowManager(Context context, TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return new RestartDialogWindowManager(context, taskInfo, mSyncQueue, taskListener,
+ mDisplayController.getDisplayLayout(taskInfo.displayId), mTransitionsLazy.get(),
+ this::onRestartDialogCallback, this::onRestartDialogDismissCallback,
+ mCompatUIConfiguration);
+ }
+
+ private void onRestartDialogCallback(
+ Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
+ mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
+ mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+ }
+
+ private void onRestartDialogDismissCallback(
+ Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
+ mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId);
+ onCompatInfoChanged(stateInfo.first, stateInfo.second);
+ }
+
private void removeLayouts(int taskId) {
final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
if (layout != null) {
@@ -338,6 +432,14 @@
mActiveLetterboxEduLayout.release();
mActiveLetterboxEduLayout = null;
}
+
+ final RestartDialogWindowManager restartLayout =
+ mTaskIdToRestartDialogWindowManagerMap.get(taskId);
+ if (restartLayout != null) {
+ restartLayout.release();
+ mTaskIdToRestartDialogWindowManagerMap.remove(taskId);
+ mSetOfTaskIdsShowingRestartDialog.remove(taskId);
+ }
}
private Context getOrCreateDisplayContext(int displayId) {
@@ -382,6 +484,14 @@
if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) {
callback.accept(mActiveLetterboxEduLayout);
}
+ for (int i = 0; i < mTaskIdToRestartDialogWindowManagerMap.size(); i++) {
+ final int taskId = mTaskIdToRestartDialogWindowManagerMap.keyAt(i);
+ final RestartDialogWindowManager layout =
+ mTaskIdToRestartDialogWindowManagerMap.get(taskId);
+ if (layout != null && condition.test(layout)) {
+ callback.accept(layout);
+ }
+ }
}
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index bce3ec4..c14704d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -21,12 +21,14 @@
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -38,6 +40,8 @@
import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import java.util.function.Consumer;
+
/**
* Window manager for the Size Compat restart button and Camera Compat control.
*/
@@ -50,6 +54,13 @@
private final CompatUICallback mCallback;
+ private final CompatUIConfiguration mCompatUIConfiguration;
+
+ private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
+
+ @NonNull
+ private TaskInfo mTaskInfo;
+
// Remember the last reported states in case visibility changes due to keyguard or IME updates.
@VisibleForTesting
boolean mHasSizeCompat;
@@ -68,12 +79,16 @@
CompatUIWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, CompatUICallback callback,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- CompatUIHintsState compatUIHintsState) {
+ CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration,
+ Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mTaskInfo = taskInfo;
mCallback = callback;
mHasSizeCompat = taskInfo.topActivityInSizeCompat;
mCameraCompatControlState = taskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
+ mCompatUIConfiguration = compatUIConfiguration;
+ mOnRestartButtonClicked = onRestartButtonClicked;
}
@Override
@@ -119,6 +134,7 @@
@Override
public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
boolean canShow) {
+ mTaskInfo = taskInfo;
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
mHasSizeCompat = taskInfo.topActivityInSizeCompat;
@@ -138,7 +154,7 @@
/** Called when the restart button is clicked. */
void onRestartButtonClicked() {
- mCallback.onSizeCompatRestartButtonClicked(mTaskId);
+ mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener()));
}
/** Called when the camera treatment button is clicked. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 2cc9f45..34e650a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -151,6 +151,7 @@
@Override
public void setConfiguration(Configuration configuration) {
super.setConfiguration(configuration);
+ // TODO(b/266262111): Investigate loss of theme configuration when switching TaskListener
mContext = mContext.createConfigurationContext(configuration);
}
@@ -168,6 +169,10 @@
return mLeash;
}
+ protected ShellTaskOrganizer.TaskListener getTaskListener() {
+ return mTaskListener;
+ }
+
/** Inits the z-order of the surface. */
private void initSurface(SurfaceControl leash) {
final int z = getZOrder();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java
new file mode 100644
index 0000000..c53e638
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java
@@ -0,0 +1,107 @@
+/*
+ * 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.wm.shell.compatui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.wm.shell.R;
+
+import java.util.function.Consumer;
+
+/**
+ * Container for a SCM restart confirmation dialog and background dim.
+ */
+public class RestartDialogLayout extends ConstraintLayout implements DialogContainerSupplier {
+
+ private View mDialogContainer;
+ private TextView mDialogTitle;
+ private Drawable mBackgroundDim;
+
+ public RestartDialogLayout(Context context) {
+ this(context, null);
+ }
+
+ public RestartDialogLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public View getDialogContainerView() {
+ return mDialogContainer;
+ }
+
+ TextView getDialogTitle() {
+ return mDialogTitle;
+ }
+
+ @Override
+ public Drawable getBackgroundDimDrawable() {
+ return mBackgroundDim;
+ }
+
+ /**
+ * Register a callback for the dismiss button and background dim.
+ *
+ * @param callback The callback to register or null if all on click listeners should be removed.
+ */
+ void setDismissOnClickListener(@Nullable Runnable callback) {
+ final OnClickListener listener = callback == null ? null : view -> callback.run();
+ findViewById(R.id.letterbox_restart_dialog_dismiss_button).setOnClickListener(listener);
+ }
+
+ /**
+ * Register a callback for the restart button
+ *
+ * @param callback The callback to register or null if all on click listeners should be removed.
+ */
+ void setRestartOnClickListener(@Nullable Consumer<Boolean> callback) {
+ final CheckBox dontShowAgainCheckbox = findViewById(R.id.letterbox_restart_dialog_checkbox);
+ final OnClickListener listener = callback == null ? null : view -> callback.accept(
+ dontShowAgainCheckbox.isChecked());
+ findViewById(R.id.letterbox_restart_dialog_restart_button).setOnClickListener(listener);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDialogContainer = findViewById(R.id.letterbox_restart_dialog_container);
+ mDialogTitle = findViewById(R.id.letterbox_restart_dialog_title);
+ mBackgroundDim = getBackground().mutate();
+ // Set the alpha of the background dim to 0 for enter animation.
+ mBackgroundDim.setAlpha(0);
+ // We add a no-op on-click listener to the dialog container so that clicks on it won't
+ // propagate to the listener of the layout (which represents the background dim).
+ mDialogContainer.setOnClickListener(view -> {});
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
new file mode 100644
index 0000000..10f25d0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
@@ -0,0 +1,259 @@
+/*
+ * 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.wm.shell.compatui;
+
+import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.provider.Settings;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.function.Consumer;
+
+/**
+ * Window manager for the Restart Dialog.
+ *
+ * TODO(b/263484314): Create abstraction of RestartDialogWindowManager and LetterboxEduWindowManager
+ */
+class RestartDialogWindowManager extends CompatUIWindowManagerAbstract {
+
+ /**
+ * The restart dialog should be the topmost child of the Task in case there can be more
+ * than one child.
+ */
+ private static final int Z_ORDER = Integer.MAX_VALUE;
+
+ private final DialogAnimationController<RestartDialogLayout> mAnimationController;
+
+ private final Transitions mTransitions;
+
+ // Remember the last reported state in case visibility changes due to keyguard or IME updates.
+ private boolean mRequestRestartDialog;
+
+ private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback;
+
+ private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartCallback;
+
+ private final CompatUIConfiguration mCompatUIConfiguration;
+
+ /**
+ * The vertical margin between the dialog container and the task stable bounds (excluding
+ * insets).
+ */
+ private final int mDialogVerticalMargin;
+
+ @NonNull
+ private TaskInfo mTaskInfo;
+
+ @Nullable
+ @VisibleForTesting
+ RestartDialogLayout mLayout;
+
+ RestartDialogWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout, Transitions transitions,
+ Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback,
+ Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
+ CompatUIConfiguration compatUIConfiguration) {
+ this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
+ onRestartCallback, onDismissCallback,
+ new DialogAnimationController<>(context, "RestartDialogWindowManager"),
+ compatUIConfiguration);
+ }
+
+ @VisibleForTesting
+ RestartDialogWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout, Transitions transitions,
+ Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback,
+ Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
+ DialogAnimationController<RestartDialogLayout> animationController,
+ CompatUIConfiguration compatUIConfiguration) {
+ super(context, taskInfo, syncQueue, taskListener, displayLayout);
+ mTaskInfo = taskInfo;
+ mTransitions = transitions;
+ mOnDismissCallback = onDismissCallback;
+ mOnRestartCallback = onRestartCallback;
+ mAnimationController = animationController;
+ mDialogVerticalMargin = (int) mContext.getResources().getDimension(
+ R.dimen.letterbox_restart_dialog_margin);
+ mCompatUIConfiguration = compatUIConfiguration;
+ }
+
+ @Override
+ protected int getZOrder() {
+ return Z_ORDER;
+ }
+
+ @Override
+ @Nullable
+ protected View getLayout() {
+ return mLayout;
+ }
+
+ @Override
+ protected void removeLayout() {
+ mLayout = null;
+ }
+
+ @Override
+ protected boolean eligibleToShowLayout() {
+ // We don't show this dialog if the user has explicitly selected so clicking on a checkbox.
+ return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null
+ || !mCompatUIConfiguration.getDontShowRestartDialogAgain(mTaskInfo));
+ }
+
+ @Override
+ protected View createLayout() {
+ mLayout = inflateLayout();
+ updateDialogMargins();
+
+ // startEnterAnimation will be called immediately if shell-transitions are disabled.
+ mTransitions.runOnIdle(this::startEnterAnimation);
+
+ return mLayout;
+ }
+
+ void setRequestRestartDialog(boolean enabled) {
+ mRequestRestartDialog = enabled;
+ }
+
+ @Override
+ public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+ boolean canShow) {
+ mTaskInfo = taskInfo;
+ return super.updateCompatInfo(taskInfo, taskListener, canShow);
+ }
+
+ private void updateDialogMargins() {
+ if (mLayout == null) {
+ return;
+ }
+ final View dialogContainer = mLayout.getDialogContainerView();
+ ViewGroup.MarginLayoutParams marginParams =
+ (ViewGroup.MarginLayoutParams) dialogContainer.getLayoutParams();
+
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
+
+ marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin;
+ marginParams.bottomMargin =
+ taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin;
+ dialogContainer.setLayoutParams(marginParams);
+ }
+
+ private RestartDialogLayout inflateLayout() {
+ return (RestartDialogLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.letterbox_restart_dialog_layout, null);
+ }
+
+ private void startEnterAnimation() {
+ if (mLayout == null) {
+ // Dialog has already been released.
+ return;
+ }
+ mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
+ this::onDialogEnterAnimationEnded);
+ }
+
+ private void onDialogEnterAnimationEnded() {
+ if (mLayout == null) {
+ // Dialog has already been released.
+ return;
+ }
+ mLayout.setDismissOnClickListener(this::onDismiss);
+ mLayout.setRestartOnClickListener(dontShowAgain -> {
+ if (mLayout != null) {
+ mLayout.setDismissOnClickListener(null);
+ mAnimationController.startExitAnimation(mLayout, () -> {
+ release();
+ });
+ }
+ if (dontShowAgain) {
+ mCompatUIConfiguration.setDontShowRestartDialogAgain(mTaskInfo);
+ }
+ mOnRestartCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+ });
+ // Focus on the dialog title for accessibility.
+ mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+
+ private void onDismiss() {
+ if (mLayout == null) {
+ return;
+ }
+
+ mLayout.setDismissOnClickListener(null);
+ mAnimationController.startExitAnimation(mLayout, () -> {
+ release();
+ mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+ });
+ }
+
+ @Override
+ public void release() {
+ mAnimationController.cancelAnimation();
+ super.release();
+ }
+
+ @Override
+ protected void onParentBoundsChanged() {
+ if (mLayout == null) {
+ return;
+ }
+ // Both the layout dimensions and dialog margins depend on the parent bounds.
+ WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams();
+ mLayout.setLayoutParams(windowLayoutParams);
+ updateDialogMargins();
+ relayout(windowLayoutParams);
+ }
+
+ @Override
+ protected void updateSurfacePosition() {
+ // Nothing to do, since the position of the surface is fixed to the top left corner (0,0)
+ // of the task (parent surface), which is the default position of a surface.
+ }
+
+ @Override
+ protected WindowManager.LayoutParams getWindowLayoutParams() {
+ final Rect taskBounds = getTaskBounds();
+ return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */
+ taskBounds.height());
+ }
+
+ @VisibleForTesting
+ boolean isTaskbarEduShowing() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 7055aca0..eee8ad8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -57,7 +57,9 @@
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
@@ -197,10 +199,11 @@
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
- DockStateReader dockStateReader) {
+ DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+ CompatUIShellCommandHandler compatUIShellCommandHandler) {
return new CompatUIController(context, shellInit, shellController, displayController,
displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
- dockStateReader);
+ dockStateReader, compatUIConfiguration, compatUIShellCommandHandler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 38099fc..21eeaa2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -535,17 +536,11 @@
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
- try {
- adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
- ActivityTaskManager.getService().startActivityFromRecents(taskId, options2);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
+ taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
- return;
}
}
mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
@@ -586,17 +581,11 @@
fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
- try {
- adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
- pendingIntent1.send();
- } catch (RemoteException | PendingIntent.CanceledException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
+ pendingIntent2 = null;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
- return;
}
}
mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index a6c4ac2..39cf5f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -629,9 +629,19 @@
RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (pendingIntent2 == null) {
+ // Launching a solo task.
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+ options1 = activityOptions.toBundle();
+ addActivityOptions(options1, null /* launchTarget */);
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ mSyncQueue.queue(wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
-
startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
splitRatio, adapter, instanceId);
}
@@ -666,9 +676,19 @@
InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
+ if (taskId == INVALID_TASK_ID) {
+ // Launching a solo task.
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+ options1 = activityOptions.toBundle();
+ addActivityOptions(options1, null /* launchTarget */);
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+ mSyncQueue.queue(wct);
+ return;
+ }
+
addActivityOptions(options1, mSideStage);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
-
startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index d4408d7..05fd889 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -95,7 +95,10 @@
private @Mock Lazy<Transitions> mMockTransitionsLazy;
private @Mock CompatUIWindowManager mMockCompatLayout;
private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
+ private @Mock RestartDialogWindowManager mMockRestartDialogLayout;
private @Mock DockStateReader mDockStateReader;
+ private @Mock CompatUIConfiguration mCompatUIConfiguration;
+ private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -113,10 +116,17 @@
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+
+ doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId();
+ doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId();
+ doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean());
+ doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
+
mShellInit = spy(new ShellInit(mMockExecutor));
mController = new CompatUIController(mContext, mShellInit, mMockShellController,
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
- mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) {
+ mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
+ mCompatUIConfiguration, mCompatUIShellCommandHandler) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -128,6 +138,12 @@
TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
return mMockLetterboxEduLayout;
}
+
+ @Override
+ RestartDialogWindowManager createRestartDialogWindowManager(Context context,
+ TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+ return mMockRestartDialogLayout;
+ }
};
mShellInit.init();
spyOn(mController);
@@ -160,6 +176,8 @@
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
// Verify that the compat controls and letterbox education are updated with new size compat
// info.
@@ -168,10 +186,12 @@
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- true);
- verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- true);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
+ verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
// Verify that compat controls and letterbox education are removed with null task listener.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
@@ -181,12 +201,14 @@
verify(mMockCompatLayout).release();
verify(mMockLetterboxEduLayout).release();
+ verify(mMockRestartDialogLayout).release();
}
@Test
public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
+ doReturn(false).when(mMockRestartDialogLayout).createLayout(anyBoolean());
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -195,6 +217,8 @@
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
@@ -202,15 +226,19 @@
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
}
@Test
public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(false).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -219,24 +247,33 @@
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
+ mController);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- true);
- verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- true);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
+ verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ true);
// Verify that the layout is created again.
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
+ mController);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
}
@@ -260,6 +297,7 @@
verify(mMockCompatLayout, never()).release();
verify(mMockLetterboxEduLayout, never()).release();
+ verify(mMockRestartDialogLayout, never()).release();
verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID),
any());
@@ -268,6 +306,7 @@
verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any());
verify(mMockCompatLayout).release();
verify(mMockLetterboxEduLayout).release();
+ verify(mMockRestartDialogLayout).release();
}
@Test
@@ -279,11 +318,13 @@
verify(mMockCompatLayout, never()).updateDisplayLayout(any());
verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(any());
+ verify(mMockRestartDialogLayout, never()).updateDisplayLayout(any());
mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration());
verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout);
}
@Test
@@ -302,12 +343,14 @@
verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout);
// No update if the insets state is the same.
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState));
verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout);
verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockRestartDialogLayout, never()).updateDisplayLayout(mMockDisplayLayout);
}
@Test
@@ -320,22 +363,26 @@
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
+ verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button remains hidden while IME is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- false);
- verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- false);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
+ verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
// Verify button is shown after IME is hidden.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
+ verify(mMockRestartDialogLayout).updateVisibility(true);
}
@Test
@@ -348,22 +395,26 @@
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
+ verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button remains hidden while keyguard is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- false);
- verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
- false);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
+ verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+ /* canShow= */ false);
// Verify button is shown after keyguard becomes not showing.
mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
+ verify(mMockRestartDialogLayout).updateVisibility(true);
}
@Test
@@ -376,20 +427,23 @@
verify(mMockCompatLayout, times(2)).updateVisibility(false);
verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
+ verify(mMockRestartDialogLayout, times(2)).updateVisibility(false);
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
// Verify button remains hidden after keyguard becomes not showing since IME is showing.
mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
+ verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button is shown after IME is not showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
+ verify(mMockRestartDialogLayout).updateVisibility(true);
}
@Test
@@ -402,20 +456,23 @@
verify(mMockCompatLayout, times(2)).updateVisibility(false);
verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
+ verify(mMockRestartDialogLayout, times(2)).updateVisibility(false);
- clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+ clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
// Verify button remains hidden after IME is hidden since keyguard is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
+ verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button is shown after keyguard becomes not showing.
mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
+ verify(mMockRestartDialogLayout).updateVisibility(true);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 7d3e718..5f294d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -31,6 +31,7 @@
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.testing.AndroidTestingRunner;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.SurfaceControlViewHost;
import android.widget.ImageButton;
@@ -45,12 +46,17 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import junit.framework.Assert;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
/**
* Tests for {@link CompatUILayout}.
*
@@ -65,20 +71,22 @@
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
+ @Mock private CompatUIConfiguration mCompatUIConfiguration;
private CompatUIWindowManager mWindowManager;
private CompatUILayout mLayout;
+ private TaskInfo mTaskInfo;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mWindowManager = new CompatUIWindowManager(mContext,
- createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
- mSyncTransactionQueue, mCallback, mTaskListener,
- new DisplayLayout(), new CompatUIHintsState());
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+ mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCompatUIConfiguration, mOnRestartButtonClicked);
mLayout = (CompatUILayout)
LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
@@ -95,8 +103,15 @@
final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
button.performClick();
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> restartCaptor =
+ ArgumentCaptor.forClass(Pair.class);
+
verify(mWindowManager).onRestartButtonClicked();
- verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+ verify(mOnRestartButtonClicked).accept(restartCaptor.capture());
+ final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = restartCaptor.getValue();
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index c4d78bb..f24a7f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -39,6 +39,7 @@
import android.app.TaskInfo;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
+import android.util.Pair;
import android.view.DisplayInfo;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -54,12 +55,17 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import junit.framework.Assert;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
/**
* Tests for {@link CompatUIWindowManager}.
*
@@ -74,20 +80,22 @@
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private CompatUILayout mLayout;
@Mock private SurfaceControlViewHost mViewHost;
+ @Mock private CompatUIConfiguration mCompatUIConfiguration;
private CompatUIWindowManager mWindowManager;
+ private TaskInfo mTaskInfo;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- mWindowManager = new CompatUIWindowManager(mContext,
- createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
- mSyncTransactionQueue, mCallback, mTaskListener,
- new DisplayLayout(), new CompatUIHintsState());
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+ mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCompatUIConfiguration, mOnRestartButtonClicked);
spyOn(mWindowManager);
doReturn(mLayout).when(mWindowManager).inflateLayout();
@@ -405,7 +413,14 @@
public void testOnRestartButtonClicked() {
mWindowManager.onRestartButtonClicked();
- verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> restartCaptor =
+ ArgumentCaptor.forClass(Pair.class);
+
+ verify(mOnRestartButtonClicked).accept(restartCaptor.capture());
+ final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = restartCaptor.getValue();
+ Assert.assertEquals(mTaskInfo, result.first);
+ Assert.assertEquals(mTaskListener, result.second);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
new file mode 100644
index 0000000..e2dcdb0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.wm.shell.compatui;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link RestartDialogLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:RestartDialogLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class RestartDialogLayoutTest extends ShellTestCase {
+
+ @Mock private Runnable mDismissCallback;
+ @Mock private Consumer<Boolean> mRestartCallback;
+
+ private RestartDialogLayout mLayout;
+ private View mDismissButton;
+ private View mRestartButton;
+ private View mDialogContainer;
+ private CheckBox mDontRepeatCheckBox;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mLayout = (RestartDialogLayout)
+ LayoutInflater.from(mContext).inflate(R.layout.letterbox_restart_dialog_layout,
+ null);
+ mDismissButton = mLayout.findViewById(R.id.letterbox_restart_dialog_dismiss_button);
+ mRestartButton = mLayout.findViewById(R.id.letterbox_restart_dialog_restart_button);
+ mDialogContainer = mLayout.findViewById(R.id.letterbox_restart_dialog_container);
+ mDontRepeatCheckBox = mLayout.findViewById(R.id.letterbox_restart_dialog_checkbox);
+ mLayout.setDismissOnClickListener(mDismissCallback);
+ mLayout.setRestartOnClickListener(mRestartCallback);
+ }
+
+ @Test
+ public void testOnFinishInflate() {
+ assertEquals(mLayout.getDialogContainerView(),
+ mLayout.findViewById(R.id.letterbox_restart_dialog_container));
+ assertEquals(mLayout.getDialogTitle(),
+ mLayout.findViewById(R.id.letterbox_restart_dialog_title));
+ assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground());
+ assertEquals(mLayout.getBackground().getAlpha(), 0);
+ }
+
+ @Test
+ public void testOnDismissButtonClicked() {
+ assertTrue(mDismissButton.performClick());
+
+ verify(mDismissCallback).run();
+ }
+
+ @Test
+ public void testOnRestartButtonClickedWithoutCheckbox() {
+ mDontRepeatCheckBox.setChecked(false);
+ assertTrue(mRestartButton.performClick());
+
+ verify(mRestartCallback).accept(false);
+ }
+
+ @Test
+ public void testOnRestartButtonClickedWithCheckbox() {
+ mDontRepeatCheckBox.setChecked(true);
+ assertTrue(mRestartButton.performClick());
+
+ verify(mRestartCallback).accept(true);
+ }
+
+ @Test
+ public void testOnBackgroundClickedDoesntDismiss() {
+ assertFalse(mLayout.performClick());
+
+ verify(mDismissCallback, never()).run();
+ }
+
+ @Test
+ public void testOnDialogContainerClicked() {
+ assertTrue(mDialogContainer.performClick());
+
+ verify(mDismissCallback, never()).run();
+ verify(mRestartCallback, never()).accept(anyBoolean());
+ }
+
+ @Test
+ public void testSetDismissOnClickListenerNull() {
+ mLayout.setDismissOnClickListener(null);
+
+ assertFalse(mDismissButton.performClick());
+ assertFalse(mLayout.performClick());
+ assertTrue(mDialogContainer.performClick());
+
+ verify(mDismissCallback, never()).run();
+ }
+
+ @Test
+ public void testSetRestartOnClickListenerNull() {
+ mLayout.setRestartOnClickListener(null);
+
+ assertFalse(mRestartButton.performClick());
+ assertFalse(mLayout.performClick());
+ assertTrue(mDialogContainer.performClick());
+
+ verify(mRestartCallback, never()).accept(anyBoolean());
+ }
+
+}
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/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 1e0c359..d0124f5 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -740,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/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/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 08f0291..c9d79ab 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -189,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;
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/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 1c76884..23611ef 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -19,7 +19,6 @@
#include <GrContextOptions.h>
#include <SkExecutor.h>
#include <SkGraphics.h>
-#include <SkMathPriv.h>
#include <math.h>
#include <utils/Trace.h>
@@ -47,13 +46,23 @@
setupCacheLimits();
}
+static inline int countLeadingZeros(uint32_t mask) {
+ // __builtin_clz(0) is undefined, so we have to detect that case.
+ return mask ? __builtin_clz(mask) : 32;
+}
+
+// Return the smallest power-of-2 >= n.
+static inline uint32_t nextPowerOfTwo(uint32_t n) {
+ return n ? (1 << (32 - countLeadingZeros(n - 1))) : 1;
+}
+
void CacheManager::setupCacheLimits() {
mMaxResourceBytes = mMaxSurfaceArea * mMemoryPolicy.surfaceSizeMultiplier;
mBackgroundResourceBytes = mMaxResourceBytes * mMemoryPolicy.backgroundRetentionPercent;
// This sets the maximum size for a single texture atlas in the GPU font cache. If
// necessary, the cache can allocate additional textures that are counted against the
// total cache limits provided to Skia.
- mMaxGpuFontAtlasBytes = GrNextSizePow2(mMaxSurfaceArea);
+ mMaxGpuFontAtlasBytes = nextPowerOfTwo(mMaxSurfaceArea);
// This sets the maximum size of the CPU font cache to be at least the same size as the
// total number of GPU font caches (i.e. 4 separate GPU atlases).
mMaxCpuFontCacheBytes = std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit());
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b769f8d..6f549dc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -194,6 +194,8 @@
ATRACE_CALL();
if (window) {
+ // Ensure the hint session is running here, away from any critical paths
+ mHintSessionWrapper.init();
mNativeSurface = std::make_unique<ReliableSurface>(window);
mNativeSurface->init();
if (enableTimeout) {
@@ -1021,6 +1023,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..8c9f65f 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -95,17 +95,13 @@
}
}
-bool HintSessionWrapper::useHintSession() {
- if (!Properties::useHintManager || !Properties::isDrawingEnabled()) return false;
- if (mHintSession) return true;
- // If session does not exist, create it;
- // this defers session creation until we try to actually use it.
- if (!mSessionValid) return false;
- return init();
-}
-
bool HintSessionWrapper::init() {
- if (mUiThreadId < 0 || mRenderThreadId < 0) return false;
+ // If it already exists, broke last time we tried this, shouldn't be running, or
+ // has bad argument values, don't even bother
+ if (mHintSession != nullptr || !mSessionValid || !Properties::useHintManager ||
+ !Properties::isDrawingEnabled() || mUiThreadId < 0 || mRenderThreadId < 0) {
+ return false;
+ }
// Assume that if we return before the end, it broke
mSessionValid = false;
@@ -130,7 +126,7 @@
}
void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) {
- if (!useHintSession()) return;
+ if (mHintSession == nullptr) return;
targetWorkDurationNanos = targetWorkDurationNanos * Properties::targetCpuTimePercentage / 100;
if (targetWorkDurationNanos != mLastTargetWorkDuration &&
targetWorkDurationNanos > kSanityCheckLowerBound &&
@@ -142,7 +138,7 @@
}
void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
- if (!useHintSession()) return;
+ if (mHintSession == nullptr) return;
if (actualDurationNanos > kSanityCheckLowerBound &&
actualDurationNanos < kSanityCheckUpperBound) {
gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
@@ -150,7 +146,7 @@
}
void HintSessionWrapper::sendLoadResetHint() {
- if (!useHintSession()) return;
+ if (mHintSession == nullptr) return;
nsecs_t now = systemTime();
if (now - mLastFrameNotification > kResetHintTimeout) {
gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
@@ -158,6 +154,11 @@
mLastFrameNotification = now;
}
+void HintSessionWrapper::sendLoadIncreaseHint() {
+ if (mHintSession == nullptr) 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..f2f1298 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -33,10 +33,10 @@
void updateTargetWorkDuration(long targetDurationNanos);
void reportActualWorkDuration(long actualDurationNanos);
void sendLoadResetHint();
+ void sendLoadIncreaseHint();
+ bool init();
private:
- bool useHintSession();
- bool init();
APerformanceHintSession* mHintSession = nullptr;
nsecs_t mLastFrameNotification = 0;
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/media/aidl/android/media/soundtrigger_middleware/OWNERS b/media/aidl/android/media/soundtrigger_middleware/OWNERS
index e5d0370..01b2cb9 100644
--- a/media/aidl/android/media/soundtrigger_middleware/OWNERS
+++ b/media/aidl/android/media/soundtrigger_middleware/OWNERS
@@ -1,2 +1,2 @@
-ytai@google.com
+atneya@google.com
elaurent@google.com
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 813929e..7077a64 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1350,12 +1350,8 @@
public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags) {
Preconditions.checkNotNull(attr, "attr must not be null");
final IAudioService service = getService();
- try {
- service.setVolumeIndexForAttributes(attr, index, flags,
- getContext().getOpPackageName(), getContext().getAttributionTag());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ int groupId = getVolumeGroupIdForAttributes(attr);
+ setVolumeGroupVolumeIndex(groupId, index, flags);
}
/**
@@ -1374,11 +1370,8 @@
public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
Preconditions.checkNotNull(attr, "attr must not be null");
final IAudioService service = getService();
- try {
- return service.getVolumeIndexForAttributes(attr);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ int groupId = getVolumeGroupIdForAttributes(attr);
+ return getVolumeGroupVolumeIndex(groupId);
}
/**
@@ -1395,11 +1388,8 @@
public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
Preconditions.checkNotNull(attr, "attr must not be null");
final IAudioService service = getService();
- try {
- return service.getMaxVolumeIndexForAttributes(attr);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ int groupId = getVolumeGroupIdForAttributes(attr);
+ return getVolumeGroupMaxVolumeIndex(groupId);
}
/**
@@ -1416,8 +1406,182 @@
public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
Preconditions.checkNotNull(attr, "attr must not be null");
final IAudioService service = getService();
+ int groupId = getVolumeGroupIdForAttributes(attr);
+ return getVolumeGroupMinVolumeIndex(groupId);
+ }
+
+ /**
+ * Returns the volume group id associated to the given {@link AudioAttributes}.
+ *
+ * @param attributes The {@link AudioAttributes} to consider.
+ * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given
+ * {@link AudioAttributes} if found,
+ * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise.
+ */
+ public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
+ Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
+ return AudioProductStrategy.getVolumeGroupIdForAudioAttributes(attributes,
+ /* fallbackOnDefault= */ false);
+ }
+
+ /**
+ * Sets the volume index for a particular group associated to given id.
+ * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)}
+ * to retrieve the volume group id supporting the given {@link AudioAttributes}.
+ *
+ * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+ * @param index The volume index to set. See
+ * {@link #getVolumeGroupMaxVolumeIndex(id)} for the largest valid value
+ * {@link #getVolumeGroupMinVolumeIndex(id)} for the lowest valid value.
+ * @param flags One or more flags.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
+ public void setVolumeGroupVolumeIndex(int groupId, int index, int flags) {
+ final IAudioService service = getService();
try {
- return service.getMinVolumeIndexForAttributes(attr);
+ service.setVolumeGroupVolumeIndex(groupId, index, flags,
+ getContext().getOpPackageName(), getContext().getAttributionTag());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current volume index for a particular group associated to given id.
+ * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)}
+ * to retrieve the volume group id supporting the given {@link AudioAttributes}.
+ *
+ * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+ * @return The current volume index for the stream.
+ * @hide
+ */
+ @SystemApi
+ @IntRange(from = 0)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
+ public int getVolumeGroupVolumeIndex(int groupId) {
+ final IAudioService service = getService();
+ try {
+ return service.getVolumeGroupVolumeIndex(groupId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the maximum volume index for a particular group associated to given id.
+ * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)}
+ * to retrieve the volume group id supporting the given {@link AudioAttributes}.
+ *
+ * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+ * @return The maximum valid volume index for the {@link AudioAttributes}.
+ * @hide
+ */
+ @SystemApi
+ @IntRange(from = 0)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
+ public int getVolumeGroupMaxVolumeIndex(int groupId) {
+ final IAudioService service = getService();
+ try {
+ return service.getVolumeGroupMaxVolumeIndex(groupId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the minimum volume index for a particular group associated to given id.
+ * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)}
+ * to retrieve the volume group id supporting the given {@link AudioAttributes}.
+ *
+ * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+ * @return The minimum valid volume index for the {@link AudioAttributes}.
+ * @hide
+ */
+ @SystemApi
+ @IntRange(from = 0)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
+ public int getVolumeGroupMinVolumeIndex(int groupId) {
+ final IAudioService service = getService();
+ try {
+ return service.getVolumeGroupMinVolumeIndex(groupId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adjusts the volume of a particular group associated to given id by one step in a direction.
+ * <p> If the volume group is associated to a stream type, it fallbacks on
+ * {@link #adjustStreamVolume(int, int, int)} for compatibility reason.
+ * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
+ * the volume group id supporting the given {@link AudioAttributes}.
+ *
+ * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+ * @param direction The direction to adjust the volume. One of
+ * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
+ * {@link #ADJUST_SAME}.
+ * @param flags One or more flags.
+ * @throws SecurityException if the adjustment triggers a Do Not Disturb change and the caller
+ * is not granted notification policy access.
+ */
+ public void adjustVolumeGroupVolume(int groupId, int direction, int flags) {
+ IAudioService service = getService();
+ try {
+ service.adjustVolumeGroupVolume(groupId, direction, flags,
+ getContext().getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get last audible volume of the group associated to given id before it was muted.
+ * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
+ * the volume group id supporting the given {@link AudioAttributes}.
+ *
+ * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+ * @return current volume if not muted, volume before muted otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
+ @IntRange(from = 0)
+ public int getLastAudibleVolumeGroupVolume(int groupId) {
+ IAudioService service = getService();
+ try {
+ return service.getLastAudibleVolumeGroupVolume(groupId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current mute state for a particular volume group associated to the given id.
+ * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve
+ * the volume group id supporting the given {@link AudioAttributes}.
+ *
+ * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+ * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id.
+ * @see #adjustVolumeGroupVolume(int, int, int)
+ */
+ public boolean isVolumeGroupMuted(int groupId) {
+ IAudioService service = getService();
+ try {
+ return service.isVolumeGroupMuted(groupId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5ba7891..ad3a44a 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -138,18 +138,25 @@
@EnforcePermission("MODIFY_AUDIO_ROUTING")
List<AudioVolumeGroup> getAudioVolumeGroups();
- @EnforcePermission("MODIFY_AUDIO_ROUTING")
- void setVolumeIndexForAttributes(in AudioAttributes aa, int index, int flags,
- String callingPackage, in String attributionTag);
+ @EnforcePermission(anyOf={"MODIFY_AUDIO_SYSTEM_SETTINGS", "MODIFY_AUDIO_ROUTING"})
+ void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage,
+ in String attributionTag);
- @EnforcePermission("MODIFY_AUDIO_ROUTING")
- int getVolumeIndexForAttributes(in AudioAttributes aa);
+ @EnforcePermission(anyOf={"MODIFY_AUDIO_SYSTEM_SETTINGS", "MODIFY_AUDIO_ROUTING"})
+ int getVolumeGroupVolumeIndex(int groupId);
- @EnforcePermission("MODIFY_AUDIO_ROUTING")
- int getMaxVolumeIndexForAttributes(in AudioAttributes aa);
+ @EnforcePermission(anyOf={"MODIFY_AUDIO_SYSTEM_SETTINGS", "MODIFY_AUDIO_ROUTING"})
+ int getVolumeGroupMaxVolumeIndex(int groupId);
- @EnforcePermission("MODIFY_AUDIO_ROUTING")
- int getMinVolumeIndexForAttributes(in AudioAttributes aa);
+ @EnforcePermission(anyOf={"MODIFY_AUDIO_SYSTEM_SETTINGS", "MODIFY_AUDIO_ROUTING"})
+ int getVolumeGroupMinVolumeIndex(int groupId);
+
+ @EnforcePermission("QUERY_AUDIO_STATE")
+ int getLastAudibleVolumeGroupVolume(int groupId);
+
+ boolean isVolumeGroupMuted(int groupId);
+
+ void adjustVolumeGroupVolume(int groupId, int direction, int flags, String callingPackage);
@EnforcePermission("QUERY_AUDIO_STATE")
int getLastAudibleStreamVolume(int streamType);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index aa7e4df..f739365 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -94,4 +94,5 @@
void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, int volume);
void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId);
+ void showMediaOutputSwitcher(String packageName);
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index a7959c5..24ec227 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -461,6 +461,19 @@
}
/**
+ * Shows the system UI output switcher.
+ */
+ public void showSystemOutputSwitcher() {
+ synchronized (mLock) {
+ try {
+ mMediaRouterService.showMediaOutputSwitcher(mPackageName);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Sets the {@link RouteListingPreference} of the app associated to this media router.
*
* <p>Use this method to inform the system UI of the routes that you would like to list for
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 0198419..f84eec6 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -233,6 +233,16 @@
/**
* @hide
+ * @return the product strategy ID (which is the generalisation of Car Audio Usage / legacy
+ * routing_strategy linked to {@link AudioAttributes#getUsage()}).
+ */
+ @SystemApi
+ @NonNull public String getName() {
+ return mName;
+ }
+
+ /**
+ * @hide
* @return first {@link AudioAttributes} associated to this product strategy.
*/
@SystemApi
diff --git a/media/java/android/media/soundtrigger/OWNERS b/media/java/android/media/soundtrigger/OWNERS
index e5d0370..01b2cb9 100644
--- a/media/java/android/media/soundtrigger/OWNERS
+++ b/media/java/android/media/soundtrigger/OWNERS
@@ -1,2 +1,2 @@
-ytai@google.com
+atneya@google.com
elaurent@google.com
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 27c2a98..ac920d2 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -1170,6 +1170,11 @@
* in Tuner 2.0 or higher version. Unsupported version will cause no-op. Use {@link
* TunerVersionChecker#getTunerVersion()} to get the version information.
*
+ * <p>Tuning with {@link
+ * android.media.tv.tuner.frontend.IptvFrontendSettings} is only supported
+ * in Tuner 3.0 or higher version. Unsupported version will cause no-op. Use {@link
+ * TunerVersionChecker#getTunerVersion()} to get the version information.
+ *
* @param settings Signal delivery information the frontend uses to
* search and lock the signal.
* @return result status of tune operation.
@@ -1198,6 +1203,12 @@
return RESULT_UNAVAILABLE;
}
}
+ if (mFrontendType == FrontendSettings.TYPE_IPTV) {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "Tuner with IPTV Frontend")) {
+ return RESULT_UNAVAILABLE;
+ }
+ }
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
mFrontendInfo = null;
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
index 2f45a70..0a1ecee 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -38,7 +38,7 @@
/** @hide */
@IntDef(prefix = "TYPE_",
value = {TYPE_UNDEFINED, TYPE_ANALOG, TYPE_ATSC, TYPE_ATSC3, TYPE_DVBC, TYPE_DVBS,
- TYPE_DVBT, TYPE_ISDBS, TYPE_ISDBS3, TYPE_ISDBT, TYPE_DTMB})
+ TYPE_DVBT, TYPE_ISDBS, TYPE_ISDBS3, TYPE_ISDBT, TYPE_DTMB, TYPE_IPTV})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
@@ -86,7 +86,10 @@
* Digital Terrestrial Multimedia Broadcast standard (DTMB) frontend type.
*/
public static final int TYPE_DTMB = FrontendType.DTMB;
-
+ /**
+ * Internet Protocol (IPTV) frontend type.
+ */
+ public static final int TYPE_IPTV = FrontendType.IPTV;
/** @hide */
@LongDef(prefix = "FEC_",
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 9fbea72..fd677ac 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -23,6 +23,7 @@
import android.annotation.SystemApi;
import android.media.tv.tuner.Lnb;
import android.media.tv.tuner.TunerVersionChecker;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
@@ -57,7 +58,10 @@
FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR,
FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE,
FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_IDS,
- FRONTEND_STATUS_TYPE_DVBT_CELL_IDS, FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO})
+ FRONTEND_STATUS_TYPE_DVBT_CELL_IDS, FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO,
+ FRONTEND_STATUS_TYPE_IPTV_CONTENT_URL, FRONTEND_STATUS_TYPE_IPTV_PACKETS_LOST,
+ FRONTEND_STATUS_TYPE_IPTV_PACKETS_RECEIVED, FRONTEND_STATUS_TYPE_IPTV_WORST_JITTER_MS,
+ FRONTEND_STATUS_TYPE_IPTV_AVERAGE_JITTER_MS})
@Retention(RetentionPolicy.SOURCE)
public @interface FrontendStatusType {}
@@ -271,6 +275,36 @@
android.hardware.tv.tuner.FrontendStatusType.DVBT_CELL_IDS;
/**
+ * IPTV content URL.
+ */
+ public static final int FRONTEND_STATUS_TYPE_IPTV_CONTENT_URL =
+ android.hardware.tv.tuner.FrontendStatusType.IPTV_CONTENT_URL;
+
+ /**
+ * IPTV packets lost.
+ */
+ public static final int FRONTEND_STATUS_TYPE_IPTV_PACKETS_LOST =
+ android.hardware.tv.tuner.FrontendStatusType.IPTV_PACKETS_LOST;
+
+ /**
+ * IPTV packets received.
+ */
+ public static final int FRONTEND_STATUS_TYPE_IPTV_PACKETS_RECEIVED =
+ android.hardware.tv.tuner.FrontendStatusType.IPTV_PACKETS_RECEIVED;
+
+ /**
+ * IPTV worst jitter.
+ */
+ public static final int FRONTEND_STATUS_TYPE_IPTV_WORST_JITTER_MS =
+ android.hardware.tv.tuner.FrontendStatusType.IPTV_WORST_JITTER_MS;
+
+ /**
+ * IPTV average jitter.
+ */
+ public static final int FRONTEND_STATUS_TYPE_IPTV_AVERAGE_JITTER_MS =
+ android.hardware.tv.tuner.FrontendStatusType.IPTV_AVERAGE_JITTER_MS;
+
+ /**
* All PLP information in a frequency band for ATSC-3.0 frontend, which includes both tuned and
* not tuned PLPs for currently watching service.
*/
@@ -519,6 +553,11 @@
private int[] mStreamIds;
private int[] mDvbtCellIds;
private Atsc3PlpInfo[] mAllPlpInfo;
+ private String mIptvContentUrl;
+ private Long mIptvPacketsLost;
+ private Long mIptvPacketsReceived;
+ private Integer mIptvWorstJitterMs;
+ private Integer mIptvAverageJitterMs;
// Constructed and fields set by JNI code.
private FrontendStatus() {
@@ -1144,4 +1183,94 @@
return mUec;
}
}
+
+ /**
+ * Gets the IPTV content URL.
+ *
+ * @return A String URL in the format protocol://ip:port (udp://127.0.0.1:3000).
+ *
+ * <p>This query is only supported by Tuner HAL 3.0 or higher. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ */
+ @NonNull
+ public String getIptvContentUrl() {
+ TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "IptvContentUrl status");
+ if (mIptvContentUrl == null) {
+ throw new IllegalStateException("IptvContentUrl status is empty");
+ }
+ return mIptvContentUrl;
+ }
+
+ /**
+ * Gets the number of packets lost.
+ *
+ * @return A long value representing the number of packets lost in transmission.
+ *
+ * <p>This query is only supported by Tuner HAL 3.0 or higher. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ */
+ @IntRange(from = 0)
+ public long getIptvPacketsLost() {
+ TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "IptvPacketsLost status");
+ if (mIptvPacketsLost == null) {
+ throw new IllegalStateException("IptvPacketsLost status is empty");
+ }
+ return mIptvPacketsLost;
+ }
+
+ /**
+ * Gets the number of packets received.
+ *
+ * @return A long value representing the number of packets received.
+ *
+ * <p>This query is only supported by Tuner HAL 3.0 or higher. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ */
+ @IntRange(from = 0)
+ public long getIptvPacketsReceived() {
+ TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "IptvPacketsReceived status");
+ if (mIptvPacketsReceived == null) {
+ throw new IllegalStateException("IptvPacketsReceived status is empty");
+ }
+ return mIptvPacketsReceived;
+ }
+
+ /**
+ * Gets the worst jitter.
+ *
+ * @return An integer representing worst jitter recorded (in milliseconds).
+ *
+ * <p>This query is only supported by Tuner HAL 3.0 or higher. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ */
+ @IntRange(from = 0)
+ public int getIptvWorstJitterMillis() {
+ TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "IptvWorstJitterMs status");
+ if (mIptvWorstJitterMs == null) {
+ throw new IllegalStateException("IptvWorstJitterMs status is empty");
+ }
+ return mIptvWorstJitterMs;
+ }
+
+ /**
+ * Gets the average jitter.
+ *
+ * @return An integer representing average jitter recorded (in milliseconds).
+ *
+ * <p>This query is only supported by Tuner HAL 3.0 or higher. Use
+ * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ */
+ @IntRange(from = 0)
+ public int getIptvAverageJitterMillis() {
+ TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_3_0, "IptvAverageJitterMs status");
+ if (mIptvAverageJitterMs == null) {
+ throw new IllegalStateException("IptvAverageJitterMs status is empty");
+ }
+ return mIptvAverageJitterMs;
+ }
}
diff --git a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java
new file mode 100644
index 0000000..fb11ea9
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java
@@ -0,0 +1,321 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.FrontendIptvSettingsIgmp;
+import android.hardware.tv.tuner.FrontendIptvSettingsProtocol;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for IPTV.
+ *
+ * @hide
+ */
+@SystemApi
+public class IptvFrontendSettings extends FrontendSettings {
+ /** @hide */
+ @IntDef(prefix = "PROTOCOL_",
+ value = {PROTOCOL_UNDEFINED, PROTOCOL_UDP, PROTOCOL_RTP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Protocol {}
+
+ /**
+ * IP protocol type UNDEFINED.
+ */
+ public static final int PROTOCOL_UNDEFINED = FrontendIptvSettingsProtocol.UNDEFINED;
+
+ /**
+ * IP protocol type UDP (User Datagram Protocol).
+ */
+ public static final int PROTOCOL_UDP = FrontendIptvSettingsProtocol.UDP;
+
+ /**
+ * IP protocol type RTP (Real-time Transport Protocol).
+ */
+ public static final int PROTOCOL_RTP = FrontendIptvSettingsProtocol.RTP;
+
+ /** @hide */
+ @IntDef(prefix = "IGMP_",
+ value = {IGMP_UNDEFINED, IGMP_V1, IGMP_V2, IGMP_V3})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Igmp {}
+
+ /**
+ * IGMP (Internet Group Management Protocol) UNDEFINED.
+ */
+ public static final int IGMP_UNDEFINED = FrontendIptvSettingsIgmp.UNDEFINED;
+
+ /**
+ * IGMP (Internet Group Management Protocol) V1.
+ */
+ public static final int IGMP_V1 = FrontendIptvSettingsIgmp.V1;
+
+ /**
+ * IGMP (Internet Group Management Protocol) V2.
+ */
+ public static final int IGMP_V2 = FrontendIptvSettingsIgmp.V2;
+
+ /**
+ * IGMP (Internet Group Management Protocol) V3.
+ */
+ public static final int IGMP_V3 = FrontendIptvSettingsIgmp.V3;
+
+ private final byte[] mSrcIpAddress;
+ private final byte[] mDstIpAddress;
+ private final int mSrcPort;
+ private final int mDstPort;
+ private final IptvFrontendSettingsFec mFec;
+ private final int mProtocol;
+ private final int mIgmp;
+ private final long mBitrate;
+ private final String mContentUrl;
+
+ public IptvFrontendSettings(@NonNull byte[] srcIpAddress, @NonNull byte[] dstIpAddress,
+ int srcPort, int dstPort, @NonNull IptvFrontendSettingsFec fec, int protocol, int igmp,
+ long bitrate, @NonNull String contentUrl) {
+ super(0);
+ mSrcIpAddress = srcIpAddress;
+ mDstIpAddress = dstIpAddress;
+ mSrcPort = srcPort;
+ mDstPort = dstPort;
+ mFec = fec;
+ mProtocol = protocol;
+ mIgmp = igmp;
+ mBitrate = bitrate;
+ mContentUrl = contentUrl;
+ }
+
+ /**
+ * Gets the source IP address.
+ */
+ @Size(min = 4, max = 16)
+ @NonNull
+ public byte[] getSrcIpAddress() {
+ return mSrcIpAddress;
+ }
+
+ /**
+ * Gets the destination IP address.
+ */
+ @Size(min = 4, max = 16)
+ @NonNull
+ public byte[] getDstIpAddress() {
+ return mDstIpAddress;
+ }
+
+ /**
+ * Gets the source port.
+ */
+ public int getSrcPort() {
+ return mSrcPort;
+ }
+
+ /**
+ * Gets the destination port.
+ */
+ public int getDstPort() {
+ return mDstPort;
+ }
+
+ /**
+ * Gets FEC (Forward Error Correction).
+ */
+ @Nullable
+ public IptvFrontendSettingsFec getFec() {
+ return mFec;
+ }
+
+ /**
+ * Gets the protocol.
+ */
+ @Protocol
+ public int getProtocol() {
+ return mProtocol;
+ }
+
+ /**
+ * Gets the IGMP (Internet Group Management Protocol).
+ */
+ @Igmp
+ public int getIgmp() {
+ return mIgmp;
+ }
+
+ /**
+ * Gets the bitrate.
+ */
+ @IntRange(from = 0)
+ public long getBitrate() {
+ return mBitrate;
+ }
+
+ /**
+ * Gets the contentUrl
+ * contentUrl is a source URL in the format protocol://ip:port containing data
+ */
+ @NonNull
+ public String getContentUrl() {
+ return mContentUrl;
+ }
+
+ /**
+ * Creates a builder for {@link IptvFrontendSettings}.
+ */
+ @NonNull
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IptvFrontendSettings}.
+ */
+ public static final class Builder {
+ private byte[] mSrcIpAddress = {0, 0, 0, 0};
+ private byte[] mDstIpAddress = {0, 0, 0, 0};
+ private int mSrcPort = 0;
+ private int mDstPort = 0;
+ private IptvFrontendSettingsFec mFec = null;
+ private int mProtocol = FrontendIptvSettingsProtocol.UNDEFINED;
+ private int mIgmp = FrontendIptvSettingsIgmp.UNDEFINED;
+ private long mBitrate = 0;
+ private String mContentUrl = "";
+
+ private Builder() {
+ }
+
+ /**
+ * Sets the source IP address.
+ *
+ * <p>Default value is 0.0.0.0, an invalid IP address.
+ */
+ @NonNull
+ public Builder setSrcIpAddress(@NonNull byte[] srcIpAddress) {
+ mSrcIpAddress = srcIpAddress;
+ return this;
+ }
+
+ /**
+ * Sets the destination IP address.
+ *
+ * <p>Default value is 0.0.0.0, an invalid IP address.
+ */
+ @NonNull
+ public Builder setDstIpAddress(@NonNull byte[] dstIpAddress) {
+ mDstIpAddress = dstIpAddress;
+ return this;
+ }
+
+ /**
+ * Sets the source IP port.
+ *
+ * <p>Default value is 0.
+ */
+ @NonNull
+ public Builder setSrcPort(int srcPort) {
+ mSrcPort = srcPort;
+ return this;
+ }
+
+ /**
+ * Sets the destination IP port.
+ *
+ * <p>Default value is 0.
+ */
+ @NonNull
+ public Builder setDstPort(int dstPort) {
+ mDstPort = dstPort;
+ return this;
+ }
+
+ /**
+ * Sets the FEC (Forward Error Correction).
+ *
+ * <p>Default value is {@code null}.
+ */
+ @NonNull
+ public Builder setFec(@Nullable IptvFrontendSettingsFec fec) {
+ mFec = fec;
+ return this;
+ }
+
+ /**
+ * Sets the protocol.
+ *
+ * <p>Default value is {@link #PROTOCOL_UNDEFINED}.
+ */
+ @NonNull
+ public Builder setProtocol(@Protocol int protocol) {
+ mProtocol = protocol;
+ return this;
+ }
+
+ /**
+ * Sets the IGMP (Internet Group Management Protocol).
+ *
+ * <p>Default value is {@link #IGMP_UNDEFINED}.
+ */
+ @NonNull
+ public Builder setIgmp(@Igmp int igmp) {
+ mIgmp = igmp;
+ return this;
+ }
+
+ /**
+ * Sets the bitrate.
+ *
+ * <p>Default value is 0.
+ */
+ @NonNull
+ public Builder setBitrate(@IntRange(from = 0) long bitrate) {
+ mBitrate = bitrate;
+ return this;
+ }
+
+ /**
+ * Sets the contentUrl.
+ *
+ * <p>Default value is "".
+ */
+ @NonNull
+ public Builder setContentUrl(@NonNull String contentUrl) {
+ mContentUrl = contentUrl;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IptvFrontendSettings} object.
+ */
+ @NonNull
+ public IptvFrontendSettings build() {
+ return new IptvFrontendSettings(mSrcIpAddress, mDstIpAddress, mSrcPort,
+ mDstPort, mFec, mProtocol, mIgmp, mBitrate, mContentUrl);
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_IPTV;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java
new file mode 100644
index 0000000..699d615
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java
@@ -0,0 +1,147 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.FrontendIptvSettingsFecType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * FEC (Forward Error Correction) for IPTV.
+ *
+ * @hide
+ */
+@SystemApi
+public class IptvFrontendSettingsFec {
+ /** @hide */
+ @IntDef(prefix = "FEC_TYPE_",
+ value = {FEC_TYPE_UNDEFINED, FEC_TYPE_COLUMN, FEC_TYPE_ROW, FEC_TYPE_COLUMN_ROW})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FecType {}
+
+ /**
+ * FEC (Forward Error Correction) type UNDEFINED.
+ */
+ public static final int FEC_TYPE_UNDEFINED = FrontendIptvSettingsFecType.UNDEFINED;
+
+ /**
+ * FEC (Forward Error Correction) type Column.
+ */
+ public static final int FEC_TYPE_COLUMN = FrontendIptvSettingsFecType.COLUMN;
+
+ /**
+ * FEC (Forward Error Correction) type ROW.
+ */
+ public static final int FEC_TYPE_ROW = FrontendIptvSettingsFecType.ROW;
+
+ /**
+ * FEC (Forward Error Correction) type Column Row.
+ */
+ public static final int FEC_TYPE_COLUMN_ROW = FrontendIptvSettingsFecType.COLUMN_ROW;
+
+ private final int mFecType;
+ private final int mFecRowNum;
+ private final int mFecColNum;
+
+ public IptvFrontendSettingsFec(@FecType int fecType, int fecRowNum, int fecColNum) {
+ mFecType = fecType;
+ mFecRowNum = fecRowNum;
+ mFecColNum = fecColNum;
+ }
+
+ /**
+ * Gets the FEC (Forward Error Correction) type.
+ */
+ @FecType
+ public int getFecType() {
+ return mFecType;
+ }
+
+ /**
+ * Get the FEC (Forward Error Correction) row number.
+ */
+ @IntRange(from = 0)
+ public int getFecRowNum() {
+ return mFecRowNum;
+ }
+
+ /**
+ * Gets the FEC (Forward Error Correction) column number.
+ */
+ @IntRange(from = 0)
+ public int getFecColNum() {
+ return mFecColNum;
+ }
+
+ /**
+ * Creates a builder for {@link IptvFrontendSettingsFec}.
+ */
+ @NonNull
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IptvFrontendSettingsFec}.
+ */
+ public static final class Builder {
+ private int mFecType;
+ private int mFecRowNum;
+ private int mFecColNum;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets the FEC (Forward Error Correction) type
+ */
+ @NonNull
+ public Builder setFecType(@FecType int fecType) {
+ mFecType = fecType;
+ return this;
+ }
+ /**
+ * Sets the FEC (Forward Error Correction) row number.
+ */
+ @NonNull
+ public Builder setFecRowNum(@IntRange(from = 0) int fecRowNum) {
+ mFecRowNum = fecRowNum;
+ return this;
+ }
+ /**
+ * Sets the FEC (Forward Error Correction) column number.
+ */
+ @NonNull
+ public Builder setFecColNum(@IntRange(from = 0) int fecColNum) {
+ mFecColNum = fecColNum;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IptvFrontendSettingsFec} object.
+ */
+ @NonNull
+ public IptvFrontendSettingsFec build() {
+ return new IptvFrontendSettingsFec(mFecType, mFecRowNum, mFecColNum);
+ }
+ }
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 1a41bc2..35ee3ee9 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -2827,6 +2827,52 @@
env->DeleteLocalRef(plpClazz);
break;
}
+ case FrontendStatus::Tag::iptvContentUrl: {
+ jfieldID field = env->GetFieldID(clazz, "mIptvContentUrl", "Ljava/lang/String;");
+ std::string iptvContentUrl = s.get<FrontendStatus::Tag::iptvContentUrl>();
+ jstring iptvContentUrlUtf8 = env->NewStringUTF(iptvContentUrl.c_str());
+ env->SetObjectField(statusObj, field, iptvContentUrlUtf8);
+ env->DeleteLocalRef(iptvContentUrlUtf8);
+ break;
+ }
+ case FrontendStatus::Tag::iptvPacketsLost: {
+ jfieldID field = env->GetFieldID(clazz, "mIptvPacketsLost", "Ljava/lang/Long;");
+ jobject newLongObj =
+ env->NewObject(longClazz, initLong,
+ s.get<FrontendStatus::Tag::iptvPacketsLost>());
+ env->SetObjectField(statusObj, field, newLongObj);
+ env->DeleteLocalRef(newLongObj);
+ break;
+ }
+ case FrontendStatus::Tag::iptvPacketsReceived: {
+ jfieldID field = env->GetFieldID(clazz, "mIptvPacketsReceived", "Ljava/lang/Long;");
+ jobject newLongObj =
+ env->NewObject(longClazz, initLong,
+ s.get<FrontendStatus::Tag::iptvPacketsReceived>());
+ env->SetObjectField(statusObj, field, newLongObj);
+ env->DeleteLocalRef(newLongObj);
+ break;
+ }
+ case FrontendStatus::Tag::iptvWorstJitterMs: {
+ jfieldID field = env->GetFieldID(clazz, "mIptvWorstJitterMs",
+ "Ljava/lang/Integer;");
+ jobject newIntegerObj =
+ env->NewObject(intClazz, initInt,
+ s.get<FrontendStatus::Tag::iptvWorstJitterMs>());
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
+ break;
+ }
+ case FrontendStatus::Tag::iptvAverageJitterMs: {
+ jfieldID field = env->GetFieldID(clazz, "mIptvAverageJitterMs",
+ "Ljava/lang/Integer;");
+ jobject newIntegerObj =
+ env->NewObject(intClazz, initInt,
+ s.get<FrontendStatus::Tag::iptvAverageJitterMs>());
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
+ break;
+ }
}
}
return statusObj;
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index cb2baa9..ae6f71c 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -143,6 +143,8 @@
<!-- [CHAR LIMIT=100] -->
<string name="uninstall_done_app">Uninstalled <xliff:g id="package_label">%1$s</xliff:g></string>
<!-- [CHAR LIMIT=100] -->
+ <string name="uninstall_done_clone_app">Deleted <xliff:g id="package_label">%1$s</xliff:g> clone</string>
+ <!-- [CHAR LIMIT=100] -->
<string name="uninstall_failed">Uninstall unsuccessful.</string>
<!-- [CHAR LIMIT=100] -->
<string name="uninstall_failed_app">Uninstalling <xliff:g id="package_label">%1$s</xliff:g> unsuccessful.</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
index b9552fc..e089aef 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
@@ -49,6 +49,7 @@
static final String EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID";
static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
+ static final String EXTRA_IS_CLONE_APP = "com.android.packageinstaller.extra.IS_CLONE_APP";
@Override
public void onReceive(Context context, Intent intent) {
@@ -84,7 +85,10 @@
case PackageInstaller.STATUS_SUCCESS:
notificationManager.cancel(uninstallId);
- Toast.makeText(context, context.getString(R.string.uninstall_done_app, appLabel),
+ boolean isCloneApp = intent.getBooleanExtra(EXTRA_IS_CLONE_APP, false);
+ Toast.makeText(context, isCloneApp
+ ? context.getString(R.string.uninstall_done_clone_app, appLabel)
+ : context.getString(R.string.uninstall_done_app, appLabel),
Toast.LENGTH_LONG).show();
return;
case PackageInstaller.STATUS_FAILURE_BLOCKED: {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 04496b9..7250bdd 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -67,6 +67,7 @@
private static final String TAG = "UninstallerActivity";
private static final String UNINSTALLING_CHANNEL = "uninstalling";
+ private boolean mIsClonedApp;
public static class DialogInfo {
public ApplicationInfo appInfo;
@@ -277,6 +278,14 @@
fragment.show(ft, "dialog");
}
+ /**
+ * Starts uninstall of app.
+ */
+ public void startUninstallProgress(boolean keepData, boolean isClonedApp) {
+ mIsClonedApp = isClonedApp;
+ startUninstallProgress(keepData);
+ }
+
public void startUninstallProgress(boolean keepData) {
boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager());
@@ -329,6 +338,7 @@
broadcastIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
broadcastIntent.putExtra(UninstallFinish.EXTRA_APP_LABEL, label);
broadcastIntent.putExtra(UninstallFinish.EXTRA_UNINSTALL_ID, uninstallId);
+ broadcastIntent.putExtra(UninstallFinish.EXTRA_IS_CLONE_APP, mIsClonedApp);
PendingIntent pendingIntent =
PendingIntent.getBroadcast(this, uninstallId, broadcastIntent,
@@ -343,7 +353,10 @@
Notification uninstallingNotification =
(new Notification.Builder(this, UNINSTALLING_CHANNEL))
.setSmallIcon(R.drawable.ic_remove).setProgress(0, 1, true)
- .setContentTitle(getString(R.string.uninstalling_app, label)).setOngoing(true)
+ .setContentTitle(mIsClonedApp
+ ? getString(R.string.uninstalling_cloned_app, label)
+ : getString(R.string.uninstalling_app, label))
+ .setOngoing(true)
.build();
notificationManager.notify(uninstallId, uninstallingNotification);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 1bbdad5..4a93bf8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -54,6 +54,7 @@
private static final String LOG_TAG = UninstallAlertDialogFragment.class.getSimpleName();
private @Nullable CheckBox mKeepData;
+ private boolean mIsClonedApp;
/**
* Get number of bytes of the app data of the package.
@@ -125,7 +126,6 @@
messageBuilder.append(" ").append(appLabel).append(".\n\n");
}
}
- boolean isClonedApp = false;
final boolean isUpdate =
((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
@@ -154,7 +154,7 @@
userName));
} else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE)
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
- isClonedApp = true;
+ mIsClonedApp = true;
messageBuilder.append(getString(
R.string.uninstall_application_text_current_user_clone_profile));
} else {
@@ -162,7 +162,7 @@
getString(R.string.uninstall_application_text_user, userName));
}
} else if (isCloneProfile(myUserHandle)) {
- isClonedApp = true;
+ mIsClonedApp = true;
messageBuilder.append(getString(
R.string.uninstall_application_text_current_user_clone_profile));
} else {
@@ -177,7 +177,7 @@
}
}
- if (isClonedApp) {
+ if (mIsClonedApp) {
dialogBuilder.setTitle(getString(R.string.cloned_app_label, appLabel));
} else {
dialogBuilder.setTitle(appLabel);
@@ -236,7 +236,7 @@
UserManager userManager = getContext().getSystemService(UserManager.class);
List<UserHandle> profiles = userManager.getUserProfiles();
for (UserHandle userHandle : profiles) {
- if (!Process.myUserHandle().equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
+ if (!userHandle.equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
cloneUser = userHandle;
break;
}
@@ -260,7 +260,7 @@
public void onClick(DialogInterface dialog, int which) {
if (which == Dialog.BUTTON_POSITIVE) {
((UninstallerActivity) getActivity()).startUninstallProgress(
- mKeepData != null && mKeepData.isChecked());
+ mKeepData != null && mKeepData.isChecked(), mIsClonedApp);
} else {
((UninstallerActivity) getActivity()).dispatchAborted();
}
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml
index b5a70d0..595e46d 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-af/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Toegelaat"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nie toegelaat nie"</string>
<string name="version_text" msgid="4001669804596458577">"weergawe <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-kloon"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml
index 3cc6e89..e2a1bf5 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-am/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"ይፈቀዳል"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"አይፈቀድም"</string>
<string name="version_text" msgid="4001669804596458577">"ሥሪት <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"የተባዛ <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml
index 2e0a9ef..9a8e7dd 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ar/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"مسموح به"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"غير مسموح به"</string>
<string name="version_text" msgid="4001669804596458577">"الإصدار <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"نسخة طبق الأصل من \"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>\""</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml
index dd27aa1..2e8140e 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-as/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"অনুমতি দিয়া হৈছে"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"অনুমতি নাই"</string>
<string name="version_text" msgid="4001669804596458577">"সংস্কৰণ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>ৰ ক্ল’ন"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml
index 1fc9c36..c6518c8 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-az/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"İcazə verildi"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"İcazə verilməyib"</string>
<string name="version_text" msgid="4001669804596458577">"versiya <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> kopyalanması"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml
index 3d5caaf..f874c6d 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-b+sr+Latn/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozvoljeno"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dozvoljeno"</string>
<string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml
index f3c3dd0..e3d3926 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-be/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дазволена"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Забаронена"</string>
<string name="version_text" msgid="4001669804596458577">"версія <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Клон \"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>\""</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml
index c16bea8..0f51436 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bg/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Разрешено"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Не е разрешено"</string>
<string name="version_text" msgid="4001669804596458577">"версия <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Копие на <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml
index a72570f..71b3239 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bn/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"অনুমোদিত"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"অননুমোদিত"</string>
<string name="version_text" msgid="4001669804596458577">"ভার্সন <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ক্লোন"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml
index 9b98057..35983f6 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-bs/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozvoljeno"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dozvoljeno"</string>
<string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Kloniranje paketa <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml
index 111abe3..70604be 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ca/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Amb permís"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Sense permís"</string>
<string name="version_text" msgid="4001669804596458577">"versió <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clon de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml
index f58e9c4..5a3e043 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-cs/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Povoleno"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepovoleno"</string>
<string name="version_text" msgid="4001669804596458577">"verze <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon balíčku <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml
index 32ae008..c53441c 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-da/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tilladt"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ikke tilladt"</string>
<string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon af <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml
index 401d8c9..b10f020 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-de/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Zulässig"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nicht zulässig"</string>
<string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-Klon"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
index ad751f5..ac4106a 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-el/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Επιτρέπεται"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Δεν επιτρέπεται"</string>
<string name="version_text" msgid="4001669804596458577">"έκδοση <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Κλώνος <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml
index b151c95..a0772f8 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rAU/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
<string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> clone"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml
index b151c95..a0772f8 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rGB/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
<string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> clone"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml
index b151c95..a0772f8 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rIN/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
<string name="version_text" msgid="4001669804596458577">"Version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> clone"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml
index b08a73f..08da5f8 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-es-rUS/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"No se permite"</string>
<string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clon de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml
index 45da42c..32ba2f9 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-es/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"No permitida"</string>
<string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clon de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
index d8f43d8..a216abc 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-et/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Lubatud"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Pole lubatud"</string>
<string name="version_text" msgid="4001669804596458577">"versioon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Üksuse <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> kloon"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml
index 6505096..798cf35 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-eu/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Baimena dauka"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ez dauka baimenik"</string>
<string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> bertsioa"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> aplikazioaren klona"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
index 616cf87..8654c64 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fa/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"مجاز"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"غیرمجاز"</string>
<string name="version_text" msgid="4001669804596458577">"نسخه <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"همتای <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
index 161f2ae..8f42d50 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fi/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Sallittu"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ei sallittu"</string>
<string name="version_text" msgid="4001669804596458577">"versio <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> klooni"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml
index 5fd70cc..a271ff5 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fr-rCA/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Autorisé"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non autorisée"</string>
<string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clone de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml
index 239a86a..30f2bcd 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-fr/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Autorisé"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non autorisé"</string>
<string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clone de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml
index 809d24d..a98e809 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-gl/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non permitida"</string>
<string name="version_text" msgid="4001669804596458577">"versión <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clon de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml
index a1de2fb..7bff012 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-gu/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"મંજૂરી છે"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"મંજૂરી નથી"</string>
<string name="version_text" msgid="4001669804596458577">"વર્ઝન <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>ની ક્લોન"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml
index 9af4a02..1a962f5 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hi/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमति है"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमति नहीं है"</string>
<string name="version_text" msgid="4001669804596458577">"वर्शन <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> का क्लोन"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml
index 8556cfb..d179415 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hr/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dopušteno"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nije dopušteno"</string>
<string name="version_text" msgid="4001669804596458577">"verzija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml
index 9d928e9..4ccc59f 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hu/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Engedélyezve"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nem engedélyezett"</string>
<string name="version_text" msgid="4001669804596458577">"verzió: <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> klónja"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml
index 064d9998..67b3e73 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-hy/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Թույլատրված է"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Արգելված է"</string>
<string name="version_text" msgid="4001669804596458577">"տարբերակ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"«<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>» հավելվածի կլոն"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml
index 8799c9b..a2a4289c 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-in/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Diizinkan"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tidak diizinkan"</string>
<string name="version_text" msgid="4001669804596458577">"versi <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clone <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml
index ff7ee6a..5c21bec 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-is/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Leyft"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ekki leyft"</string>
<string name="version_text" msgid="4001669804596458577">"útgáfa <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Afrit af <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml
index 5d67307..c8cbe76 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-it/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Consentita"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Non consentita"</string>
<string name="version_text" msgid="4001669804596458577">"versione <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clone di <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml
index 4ab76fe..49b6b1f 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-iw/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"יש הרשאה"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"אין הרשאה"</string>
<string name="version_text" msgid="4001669804596458577">"גרסה <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"שכפול של <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml
index 1c0d8d3..2f15ab4 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ja/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"許可"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"許可しない"</string>
<string name="version_text" msgid="4001669804596458577">"バージョン <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> のクローン"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml
index a4c6783..502fcb1 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ka/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"დაშვებულია"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"დაუშვებელია"</string>
<string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> ვერსია"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> კლონი"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml
index 4760d47..1b5e8ed 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-kk/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Рұқсат етілген"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Рұқсат етілмеген"</string>
<string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> нұсқасы"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> клоны"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml
index 962fb5c..2eeb5ce 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-km/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"បានអនុញ្ញាត"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"មិនអនុញ្ញាតទេ"</string>
<string name="version_text" msgid="4001669804596458577">"កំណែ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"ក្លូន <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml
index 7edec75..04e7396 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-kn/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"ಅನುಮತಿಸಲಾಗಿದೆ"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ"</string>
<string name="version_text" msgid="4001669804596458577">"ಆವೃತ್ತಿ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ಕ್ಲೋನ್"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
index 446580b..ef4ee0d 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"허용됨"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"허용되지 않음"</string>
<string name="version_text" msgid="4001669804596458577">"버전 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 클론"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml
index 2596b93..26fe636 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ky/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Уруксат берилген"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Тыюу салынган"</string>
<string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> версиясы"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> клону"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml
index 0dc64a6..e707c9d 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lo/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"ອະນຸຍາດແລ້ວ"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ບໍ່ໄດ້ອະນຸຍາດ"</string>
<string name="version_text" msgid="4001669804596458577">"ເວີຊັນ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"ໂຄລນ <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml
index 4ca01dc..8bcee5f 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lt/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Leidžiama"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Neleidžiama"</string>
<string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> versija"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"„<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>“ kopija"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml
index bf300ac..6d5017c 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-lv/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Atļauja piešķirta"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Atļauja nav piešķirta"</string>
<string name="version_text" msgid="4001669804596458577">"versija <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Lietotnes <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> klons"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
index 839e4c7..56ed2d9 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mk/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Со дозволен пристап"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Без дозволен пристап"</string>
<string name="version_text" msgid="4001669804596458577">"верзија <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Клон на <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml
index 0aad964..1090690 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ml/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"അനുവാദം ലഭിച്ചു"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"അനുവാദം ലഭിച്ചില്ല"</string>
<string name="version_text" msgid="4001669804596458577">"പതിപ്പ് <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ക്ലോൺ ചെയ്യൽ"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
index bc6e9ae..2074222 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mn/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Зөвшөөрсөн"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Зөвшөөрөөгүй"</string>
<string name="version_text" msgid="4001669804596458577">"хувилбар <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> клон"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml
index 142a60f..36ee416 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-mr/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमती असलेले"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमती नाही"</string>
<string name="version_text" msgid="4001669804596458577">"आवृत्ती <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> क्लोन"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml
index 0d39435..c2f3243 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ms/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dibenarkan"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tidak dibenarkan"</string>
<string name="version_text" msgid="4001669804596458577">"versi <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml
index f87608f..2d08eec 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-my/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"ခွင့်ပြုထားသည်"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ခွင့်ပြုမထားပါ"</string>
<string name="version_text" msgid="4001669804596458577">"ဗားရှင်း <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ပုံတူပွား"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml
index 745834d..be739a2 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-nb/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tillatt"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ikke tillatt"</string>
<string name="version_text" msgid="4001669804596458577">"versjon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon av <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml
index 4b99bae1..7f3a523 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ne/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"अनुमति दिइएका एप"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"अनुमति नदिइएका एप"</string>
<string name="version_text" msgid="4001669804596458577">"संस्करण <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> क्लोन"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml
index 9a6b767..4ea7fed 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-nl/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Toegestaan"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Niet toegestaan"</string>
<string name="version_text" msgid="4001669804596458577">"versie <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Kloon van <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml
index e562d55..501dc5c 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-or/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"ଅନୁମତି ଦିଆଯାଇଛି"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ଅନୁମତି ଦିଆଯାଇନାହିଁ"</string>
<string name="version_text" msgid="4001669804596458577">"ଭର୍ସନ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> କ୍ଲୋନ"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml
index a24ec90..28fcf0b 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pa/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"ਆਗਿਆ ਹੈ"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ਆਗਿਆ ਨਹੀਂ ਹੈ"</string>
<string name="version_text" msgid="4001669804596458577">"ਵਰਜਨ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ਦਾ ਕਲੋਨ"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
index 7da29a5..c947a66 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pl/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dozwolone"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Niedozwolone"</string>
<string name="version_text" msgid="4001669804596458577">"wersja <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klonuj: <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml
index 85a42e2..ba92ebb6 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt-rBR/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitido"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitido"</string>
<string name="version_text" msgid="4001669804596458577">"Versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clone de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml
index 9db985d..e7030df 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt-rPT/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitida"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitida"</string>
<string name="version_text" msgid="4001669804596458577">"versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clone de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml
index 85a42e2..ba92ebb6 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-pt/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permitido"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Não permitido"</string>
<string name="version_text" msgid="4001669804596458577">"Versão <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clone de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml
index e808e4a..c78e9be 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ro/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Permisă"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepermisă"</string>
<string name="version_text" msgid="4001669804596458577">"versiunea <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clonă pentru <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
index 404ed31..3507bc7 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ru/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Разрешено"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Запрещено"</string>
<string name="version_text" msgid="4001669804596458577">"версия <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Клон приложения \"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>\""</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml
index 276cbc4..6014e07 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-si/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"ඉඩ දුන්"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ඉඩ නොදෙන"</string>
<string name="version_text" msgid="4001669804596458577">"<xliff:g id="VERSION_NUM">%1$s</xliff:g> අනුවාදය"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ක්ලෝනය"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
index d8dde40..9888125 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sk/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Povolené"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nepovolené"</string>
<string name="version_text" msgid="4001669804596458577">"verzia <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
index 4cb2a40..74b3ffd 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sl/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Dovoljeno"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Ni dovoljeno"</string>
<string name="version_text" msgid="4001669804596458577">"različica <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klonirani paket <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml
index ba3d3cb..37aa7f0 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sq/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Lejohet"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Nuk lejohet"</string>
<string name="version_text" msgid="4001669804596458577">"versioni <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon i <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml
index 75caa5a..fe9b323 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sr/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дозвољено"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Није дозвољено"</string>
<string name="version_text" msgid="4001669804596458577">"верзија <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Клон апликације <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml
index e11bb12..189c26e 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sv/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Tillåts"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Tillåts inte"</string>
<string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Klon av <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml
index be04d8e..241376b 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-sw/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Inaruhusiwa"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Hairuhusiwi"</string>
<string name="version_text" msgid="4001669804596458577">"toleo la <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Nakala ya <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml
index cab94e2..b8e5d3a 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ta/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"அனுமதிக்கப்பட்டது"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"அனுமதிக்கப்படவில்லை"</string>
<string name="version_text" msgid="4001669804596458577">"பதிப்பு <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> குளோன்"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml
index 721e86a..db41032 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-te/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"అనుమతించబడినవి"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"అనుమతించబడలేదు"</string>
<string name="version_text" msgid="4001669804596458577">"వెర్షన్ <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> క్లోన్"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml
index 0844e24..ac38443 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-th/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"อนุญาต"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"ไม่อนุญาต"</string>
<string name="version_text" msgid="4001669804596458577">"เวอร์ชัน <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"โคลนของ <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml
index 1d0bead..065834b 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-tl/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Pinapahintulutan"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Hindi pinapahintulutan"</string>
<string name="version_text" msgid="4001669804596458577">"bersyon <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Clone ng <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml
index e3fcf4d..188a096 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-tr/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"İzin veriliyor"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"İzin verilmiyor"</string>
<string name="version_text" msgid="4001669804596458577">"sürüm: <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> klonu"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml
index 392738c..0a5ca4f 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-uk/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Дозволено"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Заборонено"</string>
<string name="version_text" msgid="4001669804596458577">"версія <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Копія додатка <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml
index c05c387..6b344fc 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ur/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"اجازت ہے"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"اجازت نہیں ہے"</string>
<string name="version_text" msgid="4001669804596458577">"ورژن <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> کلون"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml
index dbef24b..d5cf2b2 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-vi/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Được phép"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Không được phép"</string>
<string name="version_text" msgid="4001669804596458577">"phiên bản <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"Bản sao của <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml
index fbd6fc7..fd16dea 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rCN/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"已允许"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允许"</string>
<string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>克隆"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml
index c05e560..98071b9 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rHK/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"允許"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允許"</string>
<string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"「<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>」複製本"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
index c05e560..03efe37 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zh-rTW/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"允許"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"不允許"</string>
<string name="version_text" msgid="4001669804596458577">"版本 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"「<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>」副本"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml
index 45d11cc..088bb0f 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-zu/strings.xml
@@ -23,6 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"Kuvumelekile"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Akuvumelekile"</string>
<string name="version_text" msgid="4001669804596458577">"Uhlobo <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <!-- no translation found for cloned_app_info_label (1765651167024478391) -->
- <skip />
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"I-<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ye-clone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index a188fea..f9745e0 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -309,8 +309,8 @@
<string name="select_logd_size_dialog_title" msgid="2105401994681013578">"প্ৰতিটো লগ বাফাৰত ল\'গাৰৰ আকাৰ বাছনি কৰক"</string>
<string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"লগাৰৰ স্থায়ী ষ্ট’ৰেজৰ বস্তুবোৰ মচিবনে?"</string>
<string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"পাৰ্ছিছটেণ্ট লগাৰ ব্যৱহাৰ কৰ নিৰীক্ষণ নকৰাৰ সময়ত, আমি আপোনাৰ ডিভাইচত থকা লগাৰ ডেটা নিৱাসীক মচা দৰকাৰ।"</string>
- <string name="select_logpersist_title" msgid="447071974007104196">"ডিভাইচটোত লগাৰৰ ডেটা নিৰবচ্ছিন্নভাৱে সঞ্চয় কৰক"</string>
- <string name="select_logpersist_dialog_title" msgid="7745193591195485594">"ডিভাইচত স্থায়ীভাৱে সঞ্চয় কৰিবলৈ লগ বাফাৰবোৰ বাছনি কৰক"</string>
+ <string name="select_logpersist_title" msgid="447071974007104196">"ডিভাইচটোত লগাৰৰ ডেটা নিৰবচ্ছিন্নভাৱে ষ্ট’ৰ কৰক"</string>
+ <string name="select_logpersist_dialog_title" msgid="7745193591195485594">"ডিভাইচত স্থায়ীভাৱে ষ্ট’ৰ কৰিবলৈ লগ বাফাৰবোৰ বাছনি কৰক"</string>
<string name="select_usb_configuration_title" msgid="6339801314922294586">"ইউএছবি কনফিগাৰেশ্বন বাছনি কৰক"</string>
<string name="select_usb_configuration_dialog_title" msgid="3579567144722589237">"ইউএছবি কনফিগাৰেশ্বন বাছনি কৰক"</string>
<string name="allow_mock_location" msgid="2102650981552527884">"নকল অৱস্থানৰ অনুমতি দিয়ক"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 80d90b7..2d01b37 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -173,7 +173,7 @@
<string name="running_process_item_user_label" msgid="3988506293099805796">"Օգտատեր՝ <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"Որոշ կանխադրված կարգավորումներ կան"</string>
<string name="launch_defaults_none" msgid="8049374306261262709">"Կանխադրված կարգավորումներ չկան"</string>
- <string name="tts_settings" msgid="8130616705989351312">"Տեքստի հնչեցման կարգավորումներ"</string>
+ <string name="tts_settings" msgid="8130616705989351312">"Տեքստի հնչեցման կարգավորումներ"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"Տեքստի հնչեցում"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"Խոսքի արագությունը"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"Տեքստի արտասանման արագությունը"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 23df3be..b2a8459 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -173,7 +173,7 @@
<string name="running_process_item_user_label" msgid="3988506293099805796">"Колдонуучу: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"Айрым демейки параметрлер туураланды"</string>
<string name="launch_defaults_none" msgid="8049374306261262709">"Демейки маанилер коюлган жок"</string>
- <string name="tts_settings" msgid="8130616705989351312">"Кеп синтезаторунун жөндөөлөрү"</string>
+ <string name="tts_settings" msgid="8130616705989351312">"Кеп синтезаторунун параметрлери"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"Кеп синтезатору"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"Кеп ылдамдыгы"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"Текст айтылчу ылдамдык"</string>
@@ -219,9 +219,9 @@
<string name="development_settings_enable" msgid="4285094651288242183">"Иштеп чыгуучунун параметрлерин иштетүү"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Колдонмо өндүрүү мүмкүнчүлүктөрүн орнотуу"</string>
<string name="development_settings_not_available" msgid="355070198089140951">"Бул колдонуучуга өнүктүүрүүчү мүмкүнчүлүктөрү берилген эмес."</string>
- <string name="vpn_settings_not_available" msgid="2894137119965668920">"Бул колдонуучу VPN жөндөөлөрүн колдоно албайт"</string>
- <string name="tethering_settings_not_available" msgid="266821736434699780">"Бул колдонуучу модем режиминин жөндөөлөрүн өзгөртө албайт"</string>
- <string name="apn_settings_not_available" msgid="1147111671403342300">"Бул колдонуучу мүмкүндүк алуу түйүнүнүн аталышынын жөндөөлөрүн колдоно албайт"</string>
+ <string name="vpn_settings_not_available" msgid="2894137119965668920">"Бул колдонуучу VPN параметрлерин колдоно албайт"</string>
+ <string name="tethering_settings_not_available" msgid="266821736434699780">"Бул колдонуучу модем режиминин параметрлерин өзгөртө албайт"</string>
+ <string name="apn_settings_not_available" msgid="1147111671403342300">"Бул колдонуучу мүмкүндүк алуу түйүнүнүн аталышынын параметрлерин колдоно албайт"</string>
<string name="enable_adb" msgid="8072776357237289039">"USB аркылуу мүчүлүштүктөрдү аныктоо"</string>
<string name="enable_adb_summary" msgid="3711526030096574316">"USB компьютерге сайылганда мүчүлүштүктөрдү оңдоо режими иштейт"</string>
<string name="clear_adb_keys" msgid="3010148733140369917">"USB аркылуу мүчүлүштүктөрдү аныктоо уруксатын артка кайтаруу"</string>
@@ -322,7 +322,7 @@
<string name="adb_warning_message" msgid="8145270656419669221">"USB-жөндөө - өндүрүү максатында гана түзүлгөн. Аны компүтериңиз менен түзмөгүңүздүн ортосунда берилиштерди алмашуу, түзмөгүңүзгө колдонмолорду эскертүүсүз орнотуу жана лог берилиштерин окуу үчүн колдонсоңуз болот."</string>
<string name="adbwifi_warning_title" msgid="727104571653031865">"Мүчүлүштүктөрдү Wi-Fi аркылуу оңдоого уруксат бересизби?"</string>
<string name="adbwifi_warning_message" msgid="8005936574322702388">"Мүчүлүштүктөрдү Wi-Fi аркылуу аныктоо – өндүрүү максатында гана түзүлгөн. Аны компьютериңиз менен түзмөгүңүздүн ортосунда маалыматты алмашуу, колдонмолорду түзмөгүңүзгө эскертүүсүз орнотуу жана маалыматтар таржымалын окуу үчүн колдонсоңуз болот."</string>
- <string name="adb_keys_warning_message" msgid="2968555274488101220">"Сиз мурун USB жөндөөлөрүнө уруксат берген бардык компүтерлердин жеткиси жокко чыгарылсынбы?"</string>
+ <string name="adb_keys_warning_message" msgid="2968555274488101220">"Сиз мурун USB параметрлерине уруксат берген бардык компүтерлердин жеткиси жокко чыгарылсынбы?"</string>
<string name="dev_settings_warning_title" msgid="8251234890169074553">"Параметрлерди өзгөртүү"</string>
<string name="dev_settings_warning_message" msgid="37741686486073668">"Бул орнотуулар өндүрүүчүлөр үчүн гана берилген. Булар түзмөгүңүздүн колдонмолорун бузулушуна же туура эмес иштешине алып келиши мүмкүн."</string>
<string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Орнотулуучу колдонмону текшерүү"</string>
@@ -420,7 +420,7 @@
<string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Иштеген жок. Күйгүзүү үчүн басып коюңуз."</string>
<string name="inactive_app_active_summary" msgid="8047630990208722344">"Иштеп турат. Өчүрүү үчүн басып коюңуз."</string>
<string name="standby_bucket_summary" msgid="5128193447550429600">"Көшүү режиминдеги колдонмонун абалы:<xliff:g id="BUCKET"> %s</xliff:g>"</string>
- <string name="transcode_settings_title" msgid="2581975870429850549">"Медиа файлдарды транскоддоо жөндөөлөрү"</string>
+ <string name="transcode_settings_title" msgid="2581975870429850549">"Медиа файлдарды транскоддоо параметрлери"</string>
<string name="transcode_user_control" msgid="6176368544817731314">"Демейки жүргүзүлгөн транскоддоону өзгөртүп коюу"</string>
<string name="transcode_enable_all" msgid="2411165920039166710">"Транскоддоо жүргүзүүнү иштетүү"</string>
<string name="transcode_default" msgid="3784803084573509491">"Колдонмолордо заманбап форматтар колдоого алынат"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 166b3f0..c148eba 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -173,7 +173,7 @@
<string name="running_process_item_user_label" msgid="3988506293099805796">"အသုံးပြုသူ- <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"မူရင်းအချို့ သတ်မှတ်ပြီး"</string>
<string name="launch_defaults_none" msgid="8049374306261262709">"မူရင်း သတ်မှတ်မထားပါ။"</string>
- <string name="tts_settings" msgid="8130616705989351312">"စာ-မှ-စကားပြောင်းခြင်း ဆက်တင်များ"</string>
+ <string name="tts_settings" msgid="8130616705989351312">"စာ-မှ-စကား ဆက်တင်များ"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"စာ-မှ-စကားသို့ အထွက်"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"စကားပြောနှုန်း"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"စာတမ်းအားပြောဆိုသော အမြန်နှုန်း"</string>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index ede69be..75d28d5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -228,9 +228,6 @@
Settings.Global.APP_AUTO_RESTRICTION_ENABLED,
GlobalSettingsProto.App.AUTO_RESTRICTION_ENABLED);
dumpSetting(s, p,
- Settings.Global.FORCED_APP_STANDBY_ENABLED,
- GlobalSettingsProto.App.FORCED_APP_STANDBY_ENABLED);
- dumpSetting(s, p,
Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
GlobalSettingsProto.App.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED);
p.end(appToken);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index db7032e..01740319 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -285,7 +285,6 @@
Settings.Global.FANCY_IME_ANIMATIONS,
Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
- Settings.Global.FORCED_APP_STANDBY_ENABLED,
Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
Settings.Global.WIFI_ON_WHEN_PROXY_DISCONNECTED,
Settings.Global.FSTRIM_MANDATORY_INTERVAL,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b4598bf..e8fd226 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -369,6 +369,9 @@
<!-- Permission needed to test wallpapers supporting ambient mode -->
<uses-permission android:name="android.permission.AMBIENT_WALLPAPER" />
+ <!-- Permission needed to test wallpaper read methods -->
+ <uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" />
+
<!-- Permission required to test ContentResolver caching. -->
<uses-permission android:name="android.permission.CACHE_CONTENT" />
@@ -802,6 +805,9 @@
<!-- Permissions required for CTS test - CtsBroadcastRadioTestCases -->
<uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
+ <!-- Permission required for CTS test - ActivityCaptureCallbackTests -->
+ <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
new file mode 100644
index 0000000..1c9dabb
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
@@ -0,0 +1,6 @@
+package com.android.systemui.animation
+
+interface AnimationFeatureFlags {
+ val isPredictiveBackQsDialogAnim: Boolean
+ get() = false
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index a3ed085..e91a671 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,8 +33,13 @@
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import android.widget.FrameLayout
+import android.window.OnBackInvokedDispatcher
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CujType
+import com.android.systemui.animation.back.BackAnimationSpec
+import com.android.systemui.animation.back.applyTo
+import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
+import com.android.systemui.animation.back.onBackAnimationCallbackFrom
import kotlin.math.roundToInt
private const val TAG = "DialogLaunchAnimator"
@@ -55,8 +60,9 @@
constructor(
private val callback: Callback,
private val interactionJankMonitor: InteractionJankMonitor,
+ private val featureFlags: AnimationFeatureFlags,
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
- private val isForTesting: Boolean = false
+ private val isForTesting: Boolean = false,
) {
private companion object {
private val TIMINGS = ActivityLaunchAnimator.TIMINGS
@@ -273,15 +279,16 @@
val animatedDialog =
AnimatedDialog(
- launchAnimator,
- callback,
- interactionJankMonitor,
- controller,
+ launchAnimator = launchAnimator,
+ callback = callback,
+ interactionJankMonitor = interactionJankMonitor,
+ controller = controller,
onDialogDismissed = { openedDialogs.remove(it) },
dialog = dialog,
- animateBackgroundBoundsChange,
- animatedParent,
- isForTesting,
+ animateBackgroundBoundsChange = animateBackgroundBoundsChange,
+ parentAnimatedDialog = animatedParent,
+ forceDisableSynchronization = isForTesting,
+ featureFlags = featureFlags,
)
openedDialogs.add(animatedDialog)
@@ -517,6 +524,7 @@
* Whether synchronization should be disabled, which can be useful if we are running in a test.
*/
private val forceDisableSynchronization: Boolean,
+ private val featureFlags: AnimationFeatureFlags,
) {
/**
* The DecorView of this dialog window.
@@ -778,12 +786,45 @@
// the dialog.
dialog.setDismissOverride(this::onDialogDismissed)
+ if (featureFlags.isPredictiveBackQsDialogAnim) {
+ // TODO(b/265923095) Improve animations for QS dialogs on configuration change
+ registerOnBackInvokedCallback(targetView = dialogContentWithBackground)
+ }
+
// Show the dialog.
dialog.show()
-
moveSourceDrawingToDialog()
}
+ private fun registerOnBackInvokedCallback(targetView: View) {
+ val metrics = targetView.resources.displayMetrics
+
+ val onBackAnimationCallback =
+ onBackAnimationCallbackFrom(
+ backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(metrics),
+ displayMetrics = metrics, // TODO(b/265060720): We could remove this
+ onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) },
+ onBackInvoked = { dialog.dismiss() },
+ )
+
+ val dispatcher = dialog.onBackInvokedDispatcher
+ targetView.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ dispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ onBackAnimationCallback
+ )
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {
+ targetView.removeOnAttachStateChangeListener(this)
+ dispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback)
+ }
+ }
+ )
+ }
+
private fun moveSourceDrawingToDialog() {
if (decorView.viewRootImpl == null) {
// Make sure that we have access to the dialog view root to move the drawing to the
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/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 681e4d1..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.
*
@@ -133,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)
@@ -143,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)
@@ -155,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
@@ -171,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.
@@ -202,9 +193,8 @@
* // progress is 0.5
* animator.start()
* }
- * </code>
- * </pre>
- *
+ * ```
+ * </code> </pre>
*/
fun rebase() {
if (progress == 0f) {
@@ -266,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) {
@@ -360,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
@@ -391,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
}
@@ -407,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(
@@ -421,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 ->
@@ -439,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"
}
}
@@ -447,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) {
@@ -483,10 +477,7 @@
}
// 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.
@@ -500,10 +491,13 @@
}
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) {
@@ -517,8 +511,8 @@
}
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/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
new file mode 100644
index 0000000..f3d8b17
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.animation.back
+
+import android.util.DisplayMetrics
+import android.view.animation.Interpolator
+import android.window.BackEvent
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.util.dpToPx
+
+/** Used to convert [BackEvent] into a [BackTransformation]. */
+fun interface BackAnimationSpec {
+
+ /** Computes transformation based on a [backEvent] and sets it to [result]. */
+ fun getBackTransformation(
+ backEvent: BackEvent,
+ progressY: Float, // TODO(b/265060720): Remove progressY. Could be retrieved from backEvent
+ result: BackTransformation,
+ )
+
+ companion object
+}
+
+/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */
+fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec(
+ displayMetrics: DisplayMetrics,
+ maxMarginXdp: Float,
+ maxMarginYdp: Float,
+ minScale: Float,
+ translateXEasing: Interpolator = Interpolators.STANDARD_DECELERATE,
+ translateYEasing: Interpolator = Interpolators.LINEAR,
+ scaleEasing: Interpolator = Interpolators.STANDARD_DECELERATE,
+): BackAnimationSpec {
+ val screenWidthPx = displayMetrics.widthPixels
+ val screenHeightPx = displayMetrics.heightPixels
+
+ val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics)
+ val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics)
+ val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2
+ val maxTranslationX = maxTranslationXByScale - maxMarginXPx
+ val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2
+ val maxTranslationY = maxTranslationYByScale - maxMarginYPx
+ val minScaleReversed = 1f - minScale
+
+ return BackAnimationSpec { backEvent, progressY, result ->
+ val direction = if (backEvent.swipeEdge == BackEvent.EDGE_LEFT) 1 else -1
+ val progressX = backEvent.progress
+
+ val ratioTranslateX = translateXEasing.getInterpolation(progressX)
+ val ratioTranslateY = translateYEasing.getInterpolation(progressY)
+ val ratioScale = scaleEasing.getInterpolation(progressX)
+
+ result.apply {
+ translateX = ratioTranslateX * direction * maxTranslationX
+ translateY = ratioTranslateY * maxTranslationY
+ scale = 1f - (ratioScale * minScaleReversed)
+ }
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
new file mode 100644
index 0000000..c6b7073
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.animation.back
+
+import android.util.DisplayMetrics
+
+/**
+ * SysUI transitions - Dismiss app (ST1) Return to launching surface or place of origin
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-1-dismiss-app
+ */
+fun BackAnimationSpec.Companion.dismissAppForSysUi(
+ displayMetrics: DisplayMetrics,
+): BackAnimationSpec =
+ BackAnimationSpec.createFloatingSurfaceAnimationSpec(
+ displayMetrics = displayMetrics,
+ maxMarginXdp = 8f,
+ maxMarginYdp = 8f,
+ minScale = 0.8f,
+ )
+
+/**
+ * SysUI transitions - Cross task (ST2) Return to previous task/app, keeping the current one open
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-2-cross-task
+ */
+fun BackAnimationSpec.Companion.crossTaskForSysUi(
+ displayMetrics: DisplayMetrics,
+): BackAnimationSpec =
+ BackAnimationSpec.createFloatingSurfaceAnimationSpec(
+ displayMetrics = displayMetrics,
+ maxMarginXdp = 8f,
+ maxMarginYdp = 8f,
+ minScale = 0.8f,
+ )
+
+/**
+ * SysUI transitions - Inner area dismiss (ST3) Dismiss non-detachable surface
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-3-inner-area-dismiss
+ */
+fun BackAnimationSpec.Companion.innerAreaDismissForSysUi(
+ displayMetrics: DisplayMetrics,
+): BackAnimationSpec =
+ BackAnimationSpec.createFloatingSurfaceAnimationSpec(
+ displayMetrics = displayMetrics,
+ maxMarginXdp = 0f,
+ maxMarginYdp = 0f,
+ minScale = 0.9f,
+ )
+
+/**
+ * SysUI transitions - Floating system surfaces (ST4)
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-4-floating-system-surfaces
+ */
+fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi(
+ displayMetrics: DisplayMetrics,
+): BackAnimationSpec =
+ BackAnimationSpec.createFloatingSurfaceAnimationSpec(
+ displayMetrics = displayMetrics,
+ maxMarginXdp = 8f,
+ maxMarginYdp = 8f,
+ minScale = 0.8f,
+ )
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
new file mode 100644
index 0000000..49d1fb4
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.animation.back
+
+import android.view.View
+
+/**
+ * This object that represents the transformation to apply to the target. The properties of this
+ * object are mutable for performance reasons (avoid recreating this object)
+ */
+data class BackTransformation(
+ var translateX: Float = Float.NaN,
+ var translateY: Float = Float.NaN,
+ var scale: Float = Float.NaN,
+)
+
+/** Apply the transformation to the [targetView] */
+fun BackTransformation.applyTo(targetView: View) {
+ if (translateX.isFinite()) targetView.translationX = translateX
+ if (translateY.isFinite()) targetView.translationY = translateY
+ if (scale.isFinite()) {
+ targetView.scaleX = scale
+ targetView.scaleY = scale
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
new file mode 100644
index 0000000..33d14b1
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.animation.back
+
+import android.util.DisplayMetrics
+import android.window.BackEvent
+import android.window.OnBackAnimationCallback
+
+/**
+ * Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be
+ * called on each update passing the current [BackTransformation].
+ *
+ * Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks.
+ */
+fun onBackAnimationCallbackFrom(
+ backAnimationSpec: BackAnimationSpec,
+ displayMetrics: DisplayMetrics, // TODO(b/265060720): We could remove this
+ onBackProgressed: (BackTransformation) -> Unit,
+ onBackStarted: (BackEvent) -> Unit = {},
+ onBackInvoked: () -> Unit = {},
+ onBackCancelled: () -> Unit = {},
+): OnBackAnimationCallback {
+ return object : OnBackAnimationCallback {
+ private var initialY = 0f
+ private val lastTransformation = BackTransformation()
+
+ override fun onBackStarted(backEvent: BackEvent) {
+ initialY = backEvent.touchY
+ onBackStarted(backEvent)
+ }
+
+ override fun onBackProgressed(backEvent: BackEvent) {
+ val progressY = (backEvent.touchY - initialY) / displayMetrics.heightPixels
+
+ backAnimationSpec.getBackTransformation(
+ backEvent = backEvent,
+ progressY = progressY,
+ result = lastTransformation,
+ )
+
+ onBackProgressed(lastTransformation)
+ }
+
+ override fun onBackInvoked() {
+ onBackInvoked()
+ }
+
+ override fun onBackCancelled() {
+ onBackCancelled()
+ }
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt
new file mode 100644
index 0000000..4bc9972
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.util.DisplayMetrics
+import android.util.TypedValue
+
+/** Convert [this] number of dps to device pixels. */
+fun Number.dpToPx(context: Context): Float = dpToPx(resources = context.resources)
+
+/** Convert [this] number of dps to device pixels. */
+fun Number.dpToPx(resources: Resources): Float = dpToPx(displayMetrics = resources.displayMetrics)
+
+/** Convert [this] number of dps to device pixels. */
+fun Number.dpToPx(displayMetrics: DisplayMetrics): Float =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, toFloat(), displayMetrics)
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/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 6d970b3..3d3ccf4 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -1,7 +1,5 @@
+packages/SystemUI
--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.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
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
index dfb73a9..87b5a4c 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
@@ -16,58 +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">
- <!-- We make the shapes a rounded rectangle instead of an oval so that it can animate -->
- <!-- properly into an app/dialog. -->
- <shape 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>
- </item>
-
- <item>
- <shape android:shape="rectangle">
- <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"/>
- <corners android:radius="@dimen/keyguard_affordance_fixed_radius"/>
- </shape>
- </item>
- </layer-list>
- </item>
-
- <item>
- <layer-list>
- <item
- android:left="3dp"
- android:top="3dp"
- android:right="3dp"
- android:bottom="3dp">
- <shape 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>
- </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_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index b76de5a..e182a6a 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -24,6 +24,7 @@
android:orientation="vertical">
<LinearLayout
+ android:id="@+id/media_metadata_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
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/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6202939..f122805 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1087,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/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/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 0ee813b..ef2247f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -15,39 +15,36 @@
*/
package com.android.systemui.shared.regionsampling
+import android.app.WallpaperColors
+import android.app.WallpaperManager
import android.graphics.Color
+import android.graphics.Point
import android.graphics.Rect
+import android.graphics.RectF
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.systemui.shared.navigationbar.RegionSamplingHelper
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCallback
import java.io.PrintWriter
import java.util.concurrent.Executor
/** Class for instance of RegionSamplingHelper */
-open class RegionSampler(
- sampledView: View?,
+open class RegionSampler
+@JvmOverloads
+constructor(
+ val sampledView: View?,
mainExecutor: Executor?,
- bgExecutor: Executor?,
- regionSamplingEnabled: Boolean,
- updateFun: UpdateColorCallback
-) {
+ val bgExecutor: Executor?,
+ val regionSamplingEnabled: Boolean,
+ val updateForegroundColor: UpdateColorCallback,
+ val wallpaperManager: WallpaperManager? = WallpaperManager.getInstance(sampledView?.context)
+) : WallpaperManager.LocalWallpaperColorConsumer {
private var regionDarkness = RegionDarkness.DEFAULT
private var samplingBounds = Rect()
private val tmpScreenLocation = IntArray(2)
@VisibleForTesting var regionSampler: RegionSamplingHelper? = null
private var lightForegroundColor = Color.WHITE
private var darkForegroundColor = Color.BLACK
-
- @VisibleForTesting
- open fun createRegionSamplingHelper(
- sampledView: View,
- callback: SamplingCallback,
- mainExecutor: Executor?,
- bgExecutor: Executor?
- ): RegionSamplingHelper {
- return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
- }
+ private val displaySize = Point()
/**
* Sets the colors to be used for Dark and Light Foreground.
@@ -73,7 +70,7 @@
}
}
- private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {
+ private fun getRegionDarkness(isRegionDark: Boolean): RegionDarkness {
return if (isRegionDark) {
RegionDarkness.DARK
} else {
@@ -87,12 +84,32 @@
/** Start region sampler */
fun startRegionSampler() {
- regionSampler?.start(samplingBounds)
+ if (!regionSamplingEnabled || sampledView == null) {
+ return
+ }
+
+ val sampledRegion = calculateSampledRegion(sampledView)
+ val regions = ArrayList<RectF>()
+ val sampledRegionWithOffset = convertBounds(sampledRegion)
+ regions.add(sampledRegionWithOffset)
+
+ wallpaperManager?.removeOnColorsChangedListener(this)
+ wallpaperManager?.addOnColorsChangedListener(this, regions)
+
+ // TODO(b/265969235): conditionally set FLAG_LOCK or FLAG_SYSTEM once HS smartspace
+ // implemented
+ bgExecutor?.execute(
+ Runnable {
+ val initialSampling =
+ wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)
+ onColorsChanged(sampledRegionWithOffset, initialSampling)
+ }
+ )
}
/** Stop region sampler */
fun stopRegionSampler() {
- regionSampler?.stop()
+ wallpaperManager?.removeOnColorsChangedListener(this)
}
/** Dump region sampler */
@@ -100,43 +117,66 @@
regionSampler?.dump(pw)
}
- init {
- if (regionSamplingEnabled && sampledView != null) {
- regionSampler =
- createRegionSamplingHelper(
- sampledView,
- object : SamplingCallback {
- override fun onRegionDarknessChanged(isRegionDark: Boolean) {
- regionDarkness = convertToClockDarkness(isRegionDark)
- updateFun()
- }
- /**
- * The method getLocationOnScreen is used to obtain the view coordinates
- * relative to its left and top edges on the device screen. Directly
- * accessing the X and Y coordinates of the view returns the location
- * relative to its parent view instead.
- */
- override fun getSampledRegion(sampledView: View): Rect {
- val screenLocation = tmpScreenLocation
- sampledView.getLocationOnScreen(screenLocation)
- val left = screenLocation[0]
- val top = screenLocation[1]
- samplingBounds.left = left
- samplingBounds.top = top
- samplingBounds.right = left + sampledView.width
- samplingBounds.bottom = top + sampledView.height
- return samplingBounds
- }
+ fun calculateSampledRegion(sampledView: View): RectF {
+ val screenLocation = tmpScreenLocation
+ /**
+ * The method getLocationOnScreen is used to obtain the view coordinates relative to its
+ * left and top edges on the device screen. Directly accessing the X and Y coordinates of
+ * the view returns the location relative to its parent view instead.
+ */
+ sampledView.getLocationOnScreen(screenLocation)
+ val left = screenLocation[0]
+ val top = screenLocation[1]
- override fun isSamplingEnabled(): Boolean {
- return regionSamplingEnabled
- }
- },
- mainExecutor,
- bgExecutor
- )
- }
- regionSampler?.setWindowVisible(true)
+ samplingBounds.left = left
+ samplingBounds.top = top
+ samplingBounds.right = left + sampledView.width
+ samplingBounds.bottom = top + sampledView.height
+
+ return RectF(samplingBounds)
+ }
+
+ /**
+ * Convert the bounds of the region we want to sample from to fractional offsets because
+ * WallpaperManager requires the bounds to be between [0,1]. The wallpaper is treated as one
+ * continuous image, so if there are multiple screens, then each screen falls into a fractional
+ * range. For instance, 4 screens have the ranges [0, 0.25], [0,25, 0.5], [0.5, 0.75], [0.75,
+ * 1].
+ */
+ fun convertBounds(originalBounds: RectF): RectF {
+
+ // TODO(b/265969235): GRAB # PAGES + CURRENT WALLPAPER PAGE # FROM LAUNCHER
+ // TODO(b/265968912): remove hard-coded value once LS wallpaper supported
+ val wallpaperPageNum = 0
+ val numScreens = 1
+
+ val screenWidth = displaySize.x
+ // TODO: investigate small difference between this and the height reported in go/web-hv
+ val screenHeight = displaySize.y
+
+ val newBounds = RectF()
+ // horizontal
+ newBounds.left = ((originalBounds.left / screenWidth) + wallpaperPageNum) / numScreens
+ newBounds.right = ((originalBounds.right / screenWidth) + wallpaperPageNum) / numScreens
+ // vertical
+ newBounds.top = originalBounds.top / screenHeight
+ newBounds.bottom = originalBounds.bottom / screenHeight
+
+ return newBounds
+ }
+
+ init {
+ sampledView?.context?.display?.getSize(displaySize)
+ }
+
+ override fun onColorsChanged(area: RectF?, colors: WallpaperColors?) {
+ // update text color when wallpaper color changes
+ regionDarkness =
+ getRegionDarkness(
+ (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) !=
+ WallpaperColors.HINT_SUPPORTS_DARK_TEXT
+ )
+ updateForegroundColor()
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index a45ce42..1680b47 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -24,6 +24,7 @@
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
+import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -47,18 +48,17 @@
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.launch
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -89,7 +89,11 @@
value.largeClock.logBuffer = largeLogBuffer
value.initialize(resources, dozeAmount, 0f)
- updateRegionSamplers(value)
+
+ if (regionSamplingEnabled) {
+ clock?.smallClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
+ clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
+ }
updateFontSizes()
}
}
@@ -104,47 +108,87 @@
private var disposableHandle: DisposableHandle? = null
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
- private fun updateColors() {
+ private val mLayoutChangedListener = object : View.OnLayoutChangeListener {
+ private var currentSmallClockView: View? = null
+ private var currentLargeClockView: View? = null
+ private var currentSmallClockLocation = IntArray(2)
+ private var currentLargeClockLocation = IntArray(2)
- if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
- val wallpaperManager = WallpaperManager.getInstance(context)
- if (!wallpaperManager.lockScreenWallpaperExists()) {
- smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
- largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
- }
- } else {
- val isLightTheme = TypedValue()
- context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
- smallClockIsDark = isLightTheme.data == 0
- largeClockIsDark = isLightTheme.data == 0
+ override fun onLayoutChange(
+ view: View?,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ val parent = (view?.parent) as FrameLayout
+
+ // don't pass in negative bounds when clocks are in transition state
+ if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) {
+ return
}
+ // SMALL CLOCK
+ if (parent.id == R.id.lockscreen_clock_view) {
+ // view bounds have changed due to clock size changing (i.e. different character widths)
+ // AND/OR the view has been translated when transitioning between small and large clock
+ if (view != currentSmallClockView ||
+ !view.locationOnScreen.contentEquals(currentSmallClockLocation)) {
+ currentSmallClockView = view
+ currentSmallClockLocation = view.locationOnScreen
+ updateRegionSampler(view)
+ }
+ }
+ // LARGE CLOCK
+ else if (parent.id == R.id.lockscreen_clock_view_large) {
+ if (view != currentLargeClockView ||
+ !view.locationOnScreen.contentEquals(currentLargeClockLocation)) {
+ currentLargeClockView = view
+ currentLargeClockLocation = view.locationOnScreen
+ updateRegionSampler(view)
+ }
+ }
+ }
+ }
+
+ private fun updateColors() {
+ val wallpaperManager = WallpaperManager.getInstance(context)
+ if (regionSamplingEnabled && !wallpaperManager.lockScreenWallpaperExists()) {
+ if (regionSampler != null) {
+ if (regionSampler?.sampledView == clock?.smallClock?.view) {
+ smallClockIsDark = regionSampler!!.currentRegionDarkness().isDark
+ clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark)
+ return
+ } else if (regionSampler?.sampledView == clock?.largeClock?.view) {
+ largeClockIsDark = regionSampler!!.currentRegionDarkness().isDark
+ clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark)
+ return
+ }
+ }
+ }
+
+ val isLightTheme = TypedValue()
+ context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
+ smallClockIsDark = isLightTheme.data == 0
+ largeClockIsDark = isLightTheme.data == 0
+
clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark)
clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark)
}
- private fun updateRegionSamplers(currentClock: ClockController?) {
- smallRegionSampler?.stopRegionSampler()
- largeRegionSampler?.stopRegionSampler()
-
- smallRegionSampler = createRegionSampler(
- currentClock?.smallClock?.view,
- mainExecutor,
- bgExecutor,
- regionSamplingEnabled,
- ::updateColors
- )
-
- largeRegionSampler = createRegionSampler(
- currentClock?.largeClock?.view,
- mainExecutor,
- bgExecutor,
- regionSamplingEnabled,
- ::updateColors
- )
-
- smallRegionSampler!!.startRegionSampler()
- largeRegionSampler!!.startRegionSampler()
+ private fun updateRegionSampler(sampledRegion: View) {
+ regionSampler?.stopRegionSampler()
+ regionSampler = createRegionSampler(
+ sampledRegion,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ ::updateColors
+ )?.apply { startRegionSampler() }
updateColors()
}
@@ -155,7 +199,7 @@
bgExecutor: Executor?,
regionSamplingEnabled: Boolean,
updateColors: () -> Unit
- ): RegionSampler {
+ ): RegionSampler? {
return RegionSampler(
sampledView,
mainExecutor,
@@ -164,8 +208,7 @@
updateColors)
}
- var smallRegionSampler: RegionSampler? = null
- var largeRegionSampler: RegionSampler? = null
+ var regionSampler: RegionSampler? = null
private var smallClockIsDark = true
private var largeClockIsDark = true
@@ -232,8 +275,6 @@
configurationController.addCallback(configListener)
batteryController.addCallback(batteryCallback)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
- smallRegionSampler?.startRegionSampler()
- largeRegionSampler?.startRegionSampler()
disposableHandle = parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
listenForDozing(this)
@@ -258,8 +299,7 @@
configurationController.removeCallback(configListener)
batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
- smallRegionSampler?.stopRegionSampler()
- largeRegionSampler?.stopRegionSampler()
+ regionSampler?.stopRegionSampler()
}
private fun updateFontSizes() {
@@ -275,8 +315,7 @@
fun dump(pw: PrintWriter) {
pw.println(this)
clock?.dump(pw)
- smallRegionSampler?.dump(pw)
- largeRegionSampler?.dump(pw)
+ regionSampler?.dump(pw)
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index b159714..35cae09 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -47,7 +47,6 @@
import com.android.systemui.R;
import java.util.ArrayList;
-import java.util.Stack;
/**
* A View similar to a textView which contains password text and can animate when the text is
@@ -92,7 +91,6 @@
private final int mGravity;
private ArrayList<CharState> mTextChars = new ArrayList<>();
private String mText = "";
- private Stack<CharState> mCharPool = new Stack<>();
private int mDotSize;
private PowerManager mPM;
private int mCharPadding;
@@ -310,13 +308,7 @@
}
private CharState obtainCharState(char c) {
- CharState charState;
- if(mCharPool.isEmpty()) {
- charState = new CharState();
- } else {
- charState = mCharPool.pop();
- charState.reset();
- }
+ CharState charState = new CharState();
charState.whichChar = c;
return charState;
}
@@ -343,8 +335,6 @@
maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
charState.startRemoveAnimation(startDelay, maxDelay);
charState.removeDotSwapCallbacks();
- } else {
- mCharPool.push(charState);
}
}
if (!animated) {
@@ -421,8 +411,6 @@
public void onAnimationEnd(Animator animation) {
if (!mCancelled) {
mTextChars.remove(CharState.this);
- mCharPool.push(CharState.this);
- reset();
cancelAnimator(textTranslateAnimator);
textTranslateAnimator = null;
}
@@ -518,21 +506,6 @@
}
};
- void reset() {
- whichChar = 0;
- currentTextSizeFactor = 0.0f;
- currentDotSizeFactor = 0.0f;
- currentWidthFactor = 0.0f;
- cancelAnimator(textAnimator);
- textAnimator = null;
- cancelAnimator(dotAnimator);
- dotAnimator = null;
- cancelAnimator(widthAnimator);
- widthAnimator = null;
- currentTextTranslationY = 1.0f;
- removeDotSwapCallbacks();
- }
-
void startRemoveAnimation(long startDelay, long widthDelay) {
boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null)
|| (dotAnimator != null && dotAnimationIsGrowing);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 5d85fc96..768eeb8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -65,8 +65,7 @@
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")
+ @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 +103,7 @@
unreleasedFlag(174148361, "notification_inline_reply_animation", teamfood = true)
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
- unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
+ releasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
// TODO(b/263414400): Tracking Bug
@JvmField
@@ -217,6 +216,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")
@@ -230,7 +234,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
@@ -348,6 +353,9 @@
val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE =
unreleasedFlag(912, "media_ttt_dismiss_gesture", teamfood = true)
+ // TODO(b/266157412): Tracking Bug
+ val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -463,6 +471,21 @@
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)
+
+ // TODO(b/265639042): Tracking Bug
+ @JvmField
+ val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
+ unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
+
// 1300 - screenshots
// TODO(b/254513155): Tracking Bug
@JvmField
@@ -494,6 +517,8 @@
// 1800 - shade container
@JvmField
val LEAVE_SHADE_OPEN_FOR_BUGREPORT = releasedFlag(1800, "leave_shade_open_for_bugreport")
+ // TODO(b/265944639): Tracking Bug
+ @JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade")
// 1900
@JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
@@ -554,6 +579,5 @@
// 2600 - keyboard shortcut
// TODO(b/259352579): Tracking Bug
- @JvmField
- val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
+ @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
}
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/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 6a5e725..da2164e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -304,6 +304,7 @@
mediaTimeoutListener.stateCallback = { key: String, state: PlaybackState ->
updateState(key, state)
}
+ mediaTimeoutListener.sessionCallback = { key: String -> onSessionDestroyed(key) }
mediaResumeListener.setManager(this)
mediaDataFilter.mediaDataManager = this
@@ -1292,45 +1293,106 @@
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
- val removed = mediaEntries.remove(key)
- if (useMediaResumption && removed?.resumeAction != null && removed.isLocalSession()) {
- Log.d(TAG, "Not removing $key because resumable")
- // Move to resume key (aka package name) if that key doesn't already exist.
- val resumeAction = getResumeMediaAction(removed.resumeAction!!)
- val updated =
- removed.copy(
- token = null,
- actions = listOf(resumeAction),
- semanticActions = MediaButton(playOrPause = resumeAction),
- actionsToShowInCompact = listOf(0),
- active = false,
- resumption = true,
- isPlaying = false,
- isClearable = true
- )
- val pkg = removed.packageName
- val migrate = mediaEntries.put(pkg, updated) == null
- // Notify listeners of "new" controls when migrating or removed and update when not
- if (migrate) {
- notifyMediaDataLoaded(pkg, key, updated)
- } else {
- // Since packageName is used for the key of the resumption controls, it is
- // possible that another notification has already been reused for the resumption
- // controls of this package. In this case, rather than renaming this player as
- // packageName, just remove it and then send a update to the existing resumption
- // controls.
- notifyMediaDataRemoved(key)
- notifyMediaDataLoaded(pkg, pkg, updated)
- }
- logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId)
- return
- }
- if (removed != null) {
+ val removed = mediaEntries.remove(key) ?: return
+
+ if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
+ convertToResumePlayer(removed)
+ } else if (mediaFlags.isRetainingPlayersEnabled()) {
+ handlePossibleRemoval(removed, notificationRemoved = true)
+ } else {
notifyMediaDataRemoved(key)
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
}
}
+ private fun onSessionDestroyed(key: String) {
+ if (!mediaFlags.isRetainingPlayersEnabled()) return
+
+ if (DEBUG) Log.d(TAG, "session destroyed for $key")
+ val entry = mediaEntries.remove(key) ?: return
+ // Clear token since the session is no longer valid
+ val updated = entry.copy(token = null)
+ handlePossibleRemoval(updated)
+ }
+
+ /**
+ * Convert to resume state if the player is no longer valid and active, then notify listeners
+ * that the data was updated. Does not convert to resume state if the player is still valid, or
+ * if it was removed before becoming inactive. (Assumes that [removed] was removed from
+ * [mediaEntries] before this function was called)
+ */
+ private fun handlePossibleRemoval(removed: MediaData, notificationRemoved: Boolean = false) {
+ val key = removed.notificationKey!!
+ val hasSession = removed.token != null
+ if (hasSession && removed.semanticActions != null) {
+ // The app was using session actions, and the session is still valid: keep player
+ if (DEBUG) Log.d(TAG, "Notification removed but using session actions $key")
+ mediaEntries.put(key, removed)
+ notifyMediaDataLoaded(key, key, removed)
+ } else if (!notificationRemoved && removed.semanticActions == null) {
+ // The app was using notification actions, and notif wasn't removed yet: keep player
+ if (DEBUG) Log.d(TAG, "Session destroyed but using notification actions $key")
+ mediaEntries.put(key, removed)
+ notifyMediaDataLoaded(key, key, removed)
+ } else if (removed.active) {
+ // This player was still active - it didn't last long enough to time out: remove
+ if (DEBUG) Log.d(TAG, "Removing still-active player $key")
+ notifyMediaDataRemoved(key)
+ logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
+ } else {
+ // Convert to resume
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Notification ($notificationRemoved) and/or session " +
+ "($hasSession) gone for inactive player $key"
+ )
+ }
+ convertToResumePlayer(removed)
+ }
+ }
+
+ /** Set the given [MediaData] as a resume state player and notify listeners */
+ private fun convertToResumePlayer(data: MediaData) {
+ val key = data.notificationKey!!
+ if (DEBUG) Log.d(TAG, "Converting $key to resume")
+ // Move to resume key (aka package name) if that key doesn't already exist.
+ val resumeAction = data.resumeAction?.let { getResumeMediaAction(it) }
+ val actions = resumeAction?.let { listOf(resumeAction) } ?: emptyList()
+ val launcherIntent =
+ context.packageManager.getLaunchIntentForPackage(data.packageName)?.let {
+ PendingIntent.getActivity(context, 0, it, PendingIntent.FLAG_IMMUTABLE)
+ }
+ val updated =
+ data.copy(
+ token = null,
+ actions = actions,
+ semanticActions = MediaButton(playOrPause = resumeAction),
+ actionsToShowInCompact = listOf(0),
+ active = false,
+ resumption = true,
+ isPlaying = false,
+ isClearable = true,
+ clickIntent = launcherIntent,
+ )
+ val pkg = data.packageName
+ val migrate = mediaEntries.put(pkg, updated) == null
+ // Notify listeners of "new" controls when migrating or removed and update when not
+ Log.d(TAG, "migrating? $migrate from $key -> $pkg")
+ if (migrate) {
+ notifyMediaDataLoaded(key = pkg, oldKey = key, info = updated)
+ } else {
+ // Since packageName is used for the key of the resumption controls, it is
+ // possible that another notification has already been reused for the resumption
+ // controls of this package. In this case, rather than renaming this player as
+ // packageName, just remove it and then send a update to the existing resumption
+ // controls.
+ notifyMediaDataRemoved(key)
+ notifyMediaDataLoaded(key = pkg, oldKey = pkg, info = updated)
+ }
+ logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId)
+ }
+
fun setMediaResumptionEnabled(isEnabled: Boolean) {
if (useMediaResumption == isEnabled) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
index 7f5c82f..a898b00 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
@@ -71,6 +71,12 @@
*/
lateinit var stateCallback: (String, PlaybackState) -> Unit
+ /**
+ * Callback representing that the [MediaSession] for an active control has been destroyed
+ * @param key Media control unique identifier
+ */
+ lateinit var sessionCallback: (String) -> Unit
+
init {
statusBarStateController.addCallback(
object : StatusBarStateController.StateListener {
@@ -211,6 +217,7 @@
} else {
// For active controls, if the session is destroyed, clean up everything since we
// will need to recreate it if this key is updated later
+ sessionCallback.invoke(key)
destroy()
}
}
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 5c65c8b..4827a16 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
@@ -230,7 +230,14 @@
fun updateColorScheme(colorScheme: ColorScheme?): Boolean {
var anyChanged = false
- colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged }
+ colorTransitions.forEach {
+ val isChanged = it.updateColorScheme(colorScheme)
+
+ // Ignore changes to colorSeamless, since that is expected when toggling dark mode
+ if (it == colorSeamless) return@forEach
+
+ anyChanged = isChanged || anyChanged
+ }
colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
return anyChanged
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 5bc35ca..ab03930 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -45,4 +45,10 @@
/** Check whether we show explicit indicator on UMO */
fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR)
+
+ /**
+ * If true, keep active media controls for the lifetime of the MediaSession, regardless of
+ * whether the underlying notification was dismissed
+ */
+ fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index a9e1a4d..4803371 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -95,6 +95,7 @@
private RecyclerView mDevicesRecyclerView;
private LinearLayout mDeviceListLayout;
private LinearLayout mCastAppLayout;
+ private LinearLayout mMediaMetadataSectionLayout;
private Button mDoneButton;
private Button mStopButton;
private Button mAppButton;
@@ -240,6 +241,7 @@
mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle);
mHeaderIcon = mDialogView.requireViewById(R.id.header_icon);
mDevicesRecyclerView = mDialogView.requireViewById(R.id.list_result);
+ mMediaMetadataSectionLayout = mDialogView.requireViewById(R.id.media_metadata_section);
mDeviceListLayout = mDialogView.requireViewById(R.id.device_list);
mDoneButton = mDialogView.requireViewById(R.id.done);
mStopButton = mDialogView.requireViewById(R.id.stop);
@@ -255,21 +257,17 @@
mDevicesRecyclerView.setLayoutManager(mLayoutManager);
mDevicesRecyclerView.setAdapter(mAdapter);
mDevicesRecyclerView.setHasFixedSize(false);
- // Init header icon
- mHeaderIcon.setOnClickListener(v -> onHeaderIconClick());
// Init bottom buttons
mDoneButton.setOnClickListener(v -> dismiss());
mStopButton.setOnClickListener(v -> {
mMediaOutputController.releaseSession();
dismiss();
});
- mAppButton.setOnClickListener(v -> {
- mBroadcastSender.closeSystemDialogs();
- if (mMediaOutputController.getAppLaunchIntent() != null) {
- mContext.startActivity(mMediaOutputController.getAppLaunchIntent());
- }
- dismiss();
- });
+ mAppButton.setOnClickListener(v -> mMediaOutputController.tryToLaunchMediaApplication());
+ if (mMediaOutputController.isAdvancedLayoutSupported()) {
+ mMediaMetadataSectionLayout.setOnClickListener(
+ v -> mMediaOutputController.tryToLaunchMediaApplication());
+ }
}
@Override
@@ -560,7 +558,7 @@
@Override
public void dismissDialog() {
- dismiss();
+ mBroadcastSender.closeSystemDialogs();
}
void onHeaderIconClick() {
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 9cf672b..1587e62 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -382,6 +382,15 @@
return mContext.getPackageManager().getLaunchIntentForPackage(mPackageName);
}
+ void tryToLaunchMediaApplication() {
+ Intent launchIntent = getAppLaunchIntent();
+ if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mCallback.dismissDialog();
+ mContext.startActivity(launchIntent);
+ }
+ }
+
CharSequence getHeaderTitle() {
if (mMediaController != null) {
final MediaMetadata metadata = mMediaController.getMetadata();
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..60dd5da 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
@@ -30,8 +30,8 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
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 +78,7 @@
private val viewUtil: ViewUtil,
wakeLockBuilder: WakeLock.Builder,
systemClock: SystemClock,
+ private val rippleController: MediaTttReceiverRippleController,
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
context,
logger,
@@ -114,9 +115,6 @@
}
}
- private var maxRippleWidth: Float = 0f
- private var maxRippleHeight: Float = 0f
-
private fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
routeInfo: MediaRoute2Info,
@@ -201,41 +199,44 @@
val iconView = currentView.getAppIconView()
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
+ iconView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
}
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()
- // Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
- appIconView.postOnAnimation { view.requestAccessibilityFocus() }
- expandRipple(view.requireViewById(R.id.ripple))
+ 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)
+ 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/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 1d86343..1edb837 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -24,9 +24,11 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.MediaProjectionAppSelectorActivity
import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE
+import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
@@ -43,7 +45,6 @@
import dagger.Subcomponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import java.lang.IllegalArgumentException
import javax.inject.Qualifier
import javax.inject.Scope
import kotlinx.coroutines.CoroutineScope
@@ -69,9 +70,10 @@
): Activity
}
-/** Scoped values for [MediaProjectionAppSelectorComponent].
- * We create a scope for the activity so certain dependencies like [TaskPreviewSizeProvider]
- * could be reused. */
+/**
+ * Scoped values for [MediaProjectionAppSelectorComponent]. We create a scope for the activity so
+ * certain dependencies like [TaskPreviewSizeProvider] could be reused.
+ */
@Module
interface MediaProjectionAppSelectorModule {
@@ -83,6 +85,10 @@
@Binds
@MediaProjectionAppSelectorScope
+ fun bindRecentTaskLabelLoader(impl: ActivityTaskManagerLabelLoader): RecentTaskLabelLoader
+
+ @Binds
+ @MediaProjectionAppSelectorScope
fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider
@Binds
@@ -125,8 +131,10 @@
activity.intent.extras
?: error("MediaProjectionAppSelectorActivity should be launched with extras")
return extras.getParcelable(EXTRA_HOST_APP_USER_HANDLE)
- ?: error("MediaProjectionAppSelectorActivity should be provided with " +
- "$EXTRA_HOST_APP_USER_HANDLE extra")
+ ?: error(
+ "MediaProjectionAppSelectorActivity should be provided with " +
+ "$EXTRA_HOST_APP_USER_HANDLE extra"
+ )
}
@Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context)
@@ -146,9 +154,7 @@
/** Generates [MediaProjectionAppSelectorComponent]. */
@Subcomponent.Factory
interface Factory {
- /**
- * Create a factory to inject the activity into the graph
- */
+ /** Create a factory to inject the activity into the graph */
fun create(
@BindsInstance activity: MediaProjectionAppSelectorActivity,
@BindsInstance view: MediaProjectionAppSelectorView,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt
new file mode 100644
index 0000000..eadcb93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.mediaprojection.appselector.data
+
+import android.annotation.UserIdInt
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+interface RecentTaskLabelLoader {
+ suspend fun loadLabel(userId: Int, componentName: ComponentName): CharSequence?
+}
+
+class ActivityTaskManagerLabelLoader
+@Inject
+constructor(
+ @Background private val coroutineDispatcher: CoroutineDispatcher,
+ private val packageManager: PackageManager
+) : RecentTaskLabelLoader {
+
+ override suspend fun loadLabel(
+ @UserIdInt userId: Int,
+ componentName: ComponentName
+ ): CharSequence? =
+ withContext(coroutineDispatcher) {
+ val userHandle = UserHandle(userId)
+ val appInfo =
+ packageManager.getApplicationInfo(
+ componentName.packageName,
+ PackageManager.ApplicationInfoFlags.of(0 /* no flags */)
+ )
+ val label = packageManager.getApplicationLabel(appInfo)
+ return@withContext packageManager.getUserBadgedLabel(label, userHandle)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index 15cfeee..64f97f2 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -20,11 +20,12 @@
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
-import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.android.systemui.R
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import dagger.assisted.Assisted
@@ -40,9 +41,10 @@
@Assisted private val root: ViewGroup,
private val iconLoader: AppIconLoader,
private val thumbnailLoader: RecentTaskThumbnailLoader,
+ private val labelLoader: RecentTaskLabelLoader,
private val taskViewSizeProvider: TaskPreviewSizeProvider,
@MediaProjectionAppSelector private val scope: CoroutineScope
-) : RecyclerView.ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener {
+) : ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener {
val thumbnailView: MediaProjectionTaskView = root.requireViewById(R.id.task_thumbnail)
private val iconView: ImageView = root.requireViewById(R.id.task_icon)
@@ -64,6 +66,10 @@
val icon = iconLoader.loadIcon(task.userId, component)
iconView.setImageDrawable(icon)
}
+ launch {
+ val label = labelLoader.loadLabel(task.userId, component)
+ root.contentDescription = label
+ }
}
launch {
val thumbnail = thumbnailLoader.loadThumbnail(task.taskId)
@@ -88,10 +94,10 @@
private fun updateThumbnailSize() {
thumbnailView.layoutParams =
- thumbnailView.layoutParams.apply {
- width = taskViewSizeProvider.size.width()
- height = taskViewSizeProvider.size.height()
- }
+ thumbnailView.layoutParams.apply {
+ width = taskViewSizeProvider.size.width()
+ height = taskViewSizeProvider.size.height()
+ }
}
@AssistedFactory
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/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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b532c13..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 {
@@ -5536,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/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 7556750..a0a7586 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -53,7 +53,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.util.Pair;
-import android.util.Slog;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
import android.view.WindowInsets.Type.InsetsType;
@@ -228,7 +227,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 +694,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();
}
}
@@ -1271,8 +1270,7 @@
public void showMediaOutputSwitcher(String packageName) {
int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
- Slog.e(TAG, "Call only allowed from system server.");
- return;
+ throw new SecurityException("Call only allowed from system server.");
}
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
@@ -1407,7 +1405,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/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 9a65e34..098c617 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -25,12 +25,15 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.AnimationFeatureFlags;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
@@ -281,7 +284,8 @@
static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager,
KeyguardStateController keyguardStateController,
Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ AnimationFeatureFlags animationFeatureFlags) {
DialogLaunchAnimator.Callback callback = new DialogLaunchAnimator.Callback() {
@Override
public boolean isDreaming() {
@@ -303,6 +307,19 @@
return alternateBouncerInteractor.get().canShowAlternateBouncerForFingerprint();
}
};
- return new DialogLaunchAnimator(callback, interactionJankMonitor);
+ return new DialogLaunchAnimator(callback, interactionJankMonitor, animationFeatureFlags);
+ }
+
+ /**
+ */
+ @Provides
+ @SysUISingleton
+ static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) {
+ return new AnimationFeatureFlags() {
+ @Override
+ public boolean isPredictiveBackQsDialogAnim() {
+ return featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
+ }
+ };
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 803c282..b642ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -100,7 +100,7 @@
private val regionSamplingEnabled =
featureFlags.isEnabled(Flags.REGION_SAMPLING)
-
+ private var isContentUpdatedOnce = false
private var showNotifications = false
private var showSensitiveContentForCurrentUser = false
private var showSensitiveContentForManagedUser = false
@@ -115,19 +115,6 @@
override fun onViewAttachedToWindow(v: View) {
smartspaceViews.add(v as SmartspaceView)
- if (regionSamplingEnabled) {
- var regionSampler = RegionSampler(
- v,
- uiExecutor,
- bgExecutor,
- regionSamplingEnabled,
- updateFun
- )
- initializeTextColors(regionSampler)
- regionSampler.startRegionSampler()
- regionSamplers.put(v, regionSampler)
- }
-
connectSession()
updateTextColorFromWallpaper()
@@ -137,12 +124,6 @@
override fun onViewDetachedFromWindow(v: View) {
smartspaceViews.remove(v as SmartspaceView)
- if (regionSamplingEnabled) {
- var regionSampler = regionSamplers.getValue(v)
- regionSampler.stopRegionSampler()
- regionSamplers.remove(v)
- }
-
if (smartspaceViews.isEmpty()) {
disconnect()
}
@@ -153,6 +134,24 @@
execution.assertIsMainThread()
val filteredTargets = targets.filter(::filterSmartspaceTarget)
plugin?.onTargetsAvailable(filteredTargets)
+ if (!isContentUpdatedOnce) {
+ for (v in smartspaceViews) {
+ if (regionSamplingEnabled) {
+ var regionSampler = RegionSampler(
+ v as View,
+ uiExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ updateFun
+ )
+ initializeTextColors(regionSampler)
+ regionSamplers[v] = regionSampler
+ regionSampler.startRegionSampler()
+ }
+ updateTextColorFromWallpaper()
+ }
+ isContentUpdatedOnce = true
+ }
}
private val userTrackerCallback = object : UserTracker.Callback {
@@ -399,7 +398,8 @@
private fun updateTextColorFromWallpaper() {
val wallpaperManager = WallpaperManager.getInstance(context)
- if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists()) {
+ if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists() ||
+ regionSamplers.isEmpty()) {
val wallpaperTextColor =
Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 76252d0..e996b78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -114,10 +114,10 @@
.onStart { emit(Unit) }
// for each change, lookup the new value
.map {
- secureSettings.getBoolForUser(
+ secureSettings.getIntForUser(
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
UserHandle.USER_CURRENT,
- )
+ ) == 1
}
// perform lookups on the bg thread pool
.flowOn(bgDispatcher)
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 66632e4..856d7de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -218,7 +218,7 @@
return;
}
- mNotificationPanelViewController.expandWithoutQs();
+ mNotificationPanelViewController.expandShadeToNotifications();
}
@Override
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/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index f8c17e8..4866f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -303,7 +303,8 @@
mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null
&& editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
- mController.removeRemoteInput(mEntry, mToken);
+ // Pass null to ensure all inputs are cleared for this entry b/227115380
+ mController.removeRemoteInput(mEntry, null);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 8d5e01c..9050dad 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -29,13 +29,14 @@
import android.os.Handler
import android.os.UserHandle
import android.util.Log
-import android.view.InputDevice
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.hardware.hasInputDevice
+import com.android.systemui.shared.hardware.isAnyStylusSource
import com.android.systemui.util.NotificationChannels
import java.text.NumberFormat
import javax.inject.Inject
@@ -150,10 +151,7 @@
}
private fun hasConnectedBluetoothStylus(): Boolean {
- // TODO(b/257936830): get bt address once input api available
- return inputManager.inputDeviceIds.any { deviceId ->
- inputManager.getInputDevice(deviceId).supportsSource(InputDevice.SOURCE_STYLUS)
- }
+ return inputManager.hasInputDevice { it.isAnyStylusSource && it.bluetoothAddress != null }
}
private fun getPendingBroadcast(action: String): PendingIntent? {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index c76b127..00b2fbe 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -51,7 +51,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -140,8 +139,9 @@
@Test
fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
- verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
- verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
+ // TODO(b/266103601): delete this test and add more coverage for updateColors()
+ // verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
+ // verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
@@ -152,9 +152,6 @@
@Test
fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
- verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
- verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
-
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
captor.value.onDensityOrFontScaleChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
new file mode 100644
index 0000000..3bdbf97
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
@@ -0,0 +1,87 @@
+package com.android.systemui.animation.back
+
+import android.util.DisplayMetrics
+import android.window.BackEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private data class BackInput(val progressX: Float, val progressY: Float, val edge: Int)
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BackAnimationSpecTest : SysuiTestCase() {
+ private var displayMetrics =
+ DisplayMetrics().apply {
+ widthPixels = 100
+ heightPixels = 200
+ density = 3f
+ }
+
+ @Test
+ fun sysUi_floatingSystemSurfaces_animationValues() {
+ val maxX = 14.0f
+ val maxY = 4.0f
+ val minScale = 0.8f
+
+ val backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics)
+
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 0f, progressY = 0f, edge = BackEvent.EDGE_LEFT),
+ expected = BackTransformation(translateX = 0f, translateY = 0f, scale = 1f),
+ )
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_LEFT),
+ expected = BackTransformation(translateX = -maxX, translateY = 0f, scale = minScale),
+ )
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_RIGHT),
+ expected = BackTransformation(translateX = maxX, translateY = 0f, scale = minScale),
+ )
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 1f, progressY = 1f, edge = BackEvent.EDGE_LEFT),
+ expected = BackTransformation(translateX = -maxX, translateY = -maxY, scale = minScale),
+ )
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 0f, progressY = 1f, edge = BackEvent.EDGE_LEFT),
+ expected = BackTransformation(translateX = 0f, translateY = -maxY, scale = 1f),
+ )
+ assertBackTransformation(
+ backAnimationSpec = backAnimationSpec,
+ backInput = BackInput(progressX = 0f, progressY = -1f, edge = BackEvent.EDGE_LEFT),
+ expected = BackTransformation(translateX = 0f, translateY = maxY, scale = 1f),
+ )
+ }
+}
+
+private fun assertBackTransformation(
+ backAnimationSpec: BackAnimationSpec,
+ backInput: BackInput,
+ expected: BackTransformation,
+) {
+ val actual = BackTransformation()
+ backAnimationSpec.getBackTransformation(
+ backEvent =
+ BackEvent(
+ /* touchX = */ 0f,
+ /* touchY = */ 0f,
+ /* progress = */ backInput.progressX,
+ /* swipeEdge = */ backInput.edge,
+ ),
+ progressY = backInput.progressY,
+ result = actual
+ )
+
+ val tolerance = 0f
+ assertThat(actual.translateX).isWithin(tolerance).of(expected.translateX)
+ assertThat(actual.translateY).isWithin(tolerance).of(expected.translateY)
+ assertThat(actual.scale).isWithin(tolerance).of(expected.scale)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
new file mode 100644
index 0000000..190b3d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
@@ -0,0 +1,80 @@
+package com.android.systemui.animation.back
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BackTransformationTest : SysuiTestCase() {
+ private val targetView: View = mock()
+
+ @Test
+ fun defaultValue_noTransformation() {
+ val transformation = BackTransformation()
+
+ assertThat(transformation.translateX).isNaN()
+ assertThat(transformation.translateY).isNaN()
+ assertThat(transformation.scale).isNaN()
+ }
+
+ @Test
+ fun applyTo_targetView_translateX_Y_Scale() {
+ val transformation = BackTransformation(translateX = 0f, translateY = 0f, scale = 1f)
+
+ transformation.applyTo(targetView = targetView)
+
+ verify(targetView).translationX = 0f
+ verify(targetView).translationY = 0f
+ verify(targetView).scaleX = 1f
+ verify(targetView).scaleY = 1f
+ verifyNoMoreInteractions(targetView)
+ }
+
+ @Test
+ fun applyTo_targetView_translateX() {
+ val transformation = BackTransformation(translateX = 1f)
+
+ transformation.applyTo(targetView = targetView)
+
+ verify(targetView).translationX = 1f
+ verifyNoMoreInteractions(targetView)
+ }
+
+ @Test
+ fun applyTo_targetView_translateY() {
+ val transformation = BackTransformation(translateY = 2f)
+
+ transformation.applyTo(targetView = targetView)
+
+ verify(targetView).translationY = 2f
+ verifyNoMoreInteractions(targetView)
+ }
+
+ @Test
+ fun applyTo_targetView_scale() {
+ val transformation = BackTransformation(scale = 3f)
+
+ transformation.applyTo(targetView = targetView)
+
+ verify(targetView).scaleX = 3f
+ verify(targetView).scaleY = 3f
+ verifyNoMoreInteractions(targetView)
+ }
+
+ @Test
+ fun applyTo_targetView_noTransformation() {
+ val transformation = BackTransformation()
+
+ transformation.applyTo(targetView = targetView)
+
+ verifyNoMoreInteractions(targetView)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
new file mode 100644
index 0000000..921f9a8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
@@ -0,0 +1,63 @@
+package com.android.systemui.animation.back
+
+import android.util.DisplayMetrics
+import android.window.BackEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class OnBackAnimationCallbackExtensionTest : SysuiTestCase() {
+ private val onBackProgress: (BackTransformation) -> Unit = mock()
+ private val onBackStart: (BackEvent) -> Unit = mock()
+ private val onBackInvoke: () -> Unit = mock()
+ private val onBackCancel: () -> Unit = mock()
+
+ private val displayMetrics =
+ DisplayMetrics().apply {
+ widthPixels = 100
+ heightPixels = 100
+ density = 1f
+ }
+
+ private val onBackAnimationCallback =
+ onBackAnimationCallbackFrom(
+ backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics),
+ displayMetrics = displayMetrics,
+ onBackProgressed = onBackProgress,
+ onBackStarted = onBackStart,
+ onBackInvoked = onBackInvoke,
+ onBackCancelled = onBackCancel,
+ )
+
+ @Test
+ fun onBackProgressed_shouldInvoke_onBackProgress() {
+ val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+ onBackAnimationCallback.onBackStarted(backEvent)
+
+ onBackAnimationCallback.onBackProgressed(backEvent)
+
+ verify(onBackProgress).invoke(BackTransformation(0f, 0f, 1f))
+ }
+
+ @Test
+ fun onBackStarted_shouldInvoke_onBackStart() {
+ val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+
+ onBackAnimationCallback.onBackStarted(backEvent)
+
+ verify(onBackStart).invoke(backEvent)
+ }
+
+ @Test
+ fun onBackInvoked_shouldInvoke_onBackInvoke() {
+ onBackAnimationCallback.onBackInvoked()
+
+ verify(onBackInvoke).invoke()
+ }
+}
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/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 1687fdc..1ac6695 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -134,7 +134,8 @@
private val clock = FakeSystemClock()
@Mock private lateinit var tunerService: TunerService
@Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
- @Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+ @Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+ @Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit>
@Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
@@ -184,6 +185,8 @@
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
+ verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
+ verify(mediaTimeoutListener).sessionCallback = capture(sessionCallbackCaptor)
session = MediaSession(context, "MediaDataManagerTestSession")
mediaNotification =
SbnBuilder().run {
@@ -230,6 +233,7 @@
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
}
@@ -547,6 +551,7 @@
mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -558,9 +563,21 @@
)
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
- val resumableData = data.copy(resumeAction = Runnable {})
- mediaDataManager.onMediaDataLoaded(KEY, null, resumableData)
- mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData)
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY_2),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ val data2 = mediaDataCaptor.value
+ assertThat(data2.resumption).isFalse()
+
+ mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+ mediaDataManager.onMediaDataLoaded(KEY_2, null, data2.copy(resumeAction = Runnable {}))
reset(listener)
// WHEN the first is removed
mediaDataManager.onNotificationRemoved(KEY)
@@ -1310,11 +1327,10 @@
fun testPlaybackStateChange_keyExists_callsListener() {
// Notification has been added
addNotificationAndLoad()
- verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
// Callback gets an updated state
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
- callbackCaptor.value.invoke(KEY, state)
+ stateCallbackCaptor.value.invoke(KEY, state)
// Listener is notified of updated state
verify(listener)
@@ -1332,11 +1348,10 @@
@Test
fun testPlaybackStateChange_keyDoesNotExist_doesNothing() {
val state = PlaybackState.Builder().build()
- verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
// No media added with this key
- callbackCaptor.value.invoke(KEY, state)
+ stateCallbackCaptor.value.invoke(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -1352,10 +1367,9 @@
// And then get a state update
val state = PlaybackState.Builder().build()
- verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
// Then no changes are made
- callbackCaptor.value.invoke(KEY, state)
+ stateCallbackCaptor.value.invoke(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -1367,8 +1381,7 @@
whenever(controller.playbackState).thenReturn(state)
addNotificationAndLoad()
- verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
- callbackCaptor.value.invoke(KEY, state)
+ stateCallbackCaptor.value.invoke(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -1410,8 +1423,7 @@
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
- verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
- callbackCaptor.value.invoke(PACKAGE_NAME, state)
+ stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
verify(listener)
.onMediaDataLoaded(
@@ -1436,8 +1448,7 @@
.build()
addNotificationAndLoad()
- verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
- callbackCaptor.value.invoke(KEY, state)
+ stateCallbackCaptor.value.invoke(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -1485,6 +1496,177 @@
assertThat(mediaDataCaptor.value.isClearable).isFalse()
}
+ @Test
+ fun testRetain_notifPlayer_notifRemoved_setToResume() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+
+ // When a media control based on notification is added, times out, and then removed
+ addNotificationAndLoad()
+ mediaDataManager.setTimedOut(KEY, timedOut = true)
+ assertThat(mediaDataCaptor.value.active).isFalse()
+ mediaDataManager.onNotificationRemoved(KEY)
+
+ // It is converted to a resume player
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.active).isFalse()
+ verify(logger)
+ .logActiveConvertedToResume(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId)
+ )
+ }
+
+ @Test
+ fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+
+ // When a media control based on notification is added and times out
+ addNotificationAndLoad()
+ mediaDataManager.setTimedOut(KEY, timedOut = true)
+ assertThat(mediaDataCaptor.value.active).isFalse()
+
+ // and then the session is destroyed
+ sessionCallbackCaptor.value.invoke(KEY)
+
+ // It remains as a regular player
+ verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never())
+ .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+ }
+
+ @Test
+ fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+
+ // When a media control based on notification is added and then removed, without timing out
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ assertThat(data.active).isTrue()
+ mediaDataManager.onNotificationRemoved(KEY)
+
+ // It is fully removed
+ verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+ verify(listener, never())
+ .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+ }
+
+ @Test
+ fun testRetain_canResume_removeWhileActive_setToResume() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+
+ // When a media control that supports resumption is added
+ addNotificationAndLoad()
+ val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+ mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+ // And then removed while still active
+ mediaDataManager.onNotificationRemoved(KEY)
+
+ // It is converted to a resume player
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.active).isFalse()
+ verify(logger)
+ .logActiveConvertedToResume(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId)
+ )
+ }
+
+ @Test
+ fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ addPlaybackStateAction()
+
+ // When a media control with PlaybackState actions is added, times out,
+ // and then the notification is removed
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ assertThat(data.active).isTrue()
+ mediaDataManager.setTimedOut(KEY, timedOut = true)
+ mediaDataManager.onNotificationRemoved(KEY)
+
+ // It remains as a regular player
+ verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never())
+ .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+ }
+
+ @Test
+ fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ addPlaybackStateAction()
+
+ // When a media control with PlaybackState actions is added, times out,
+ // and then the session is destroyed
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ assertThat(data.active).isTrue()
+ mediaDataManager.setTimedOut(KEY, timedOut = true)
+ sessionCallbackCaptor.value.invoke(KEY)
+
+ // It is converted to a resume player
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.active).isFalse()
+ verify(logger)
+ .logActiveConvertedToResume(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId)
+ )
+ }
+
+ @Test
+ fun testRetain_sessionPlayer_destroyedWhileActive_fullyRemoved() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ addPlaybackStateAction()
+
+ // When a media control using session actions is added, and then the session is destroyed
+ // without timing out first
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ assertThat(data.active).isTrue()
+ sessionCallbackCaptor.value.invoke(KEY)
+
+ // It is fully removed
+ verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+ verify(listener, never())
+ .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+ }
+
/** Helper function to add a media notification and capture the resulting MediaData */
private fun addNotificationAndLoad() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
@@ -1500,4 +1682,12 @@
eq(false)
)
}
+
+ /** Helper function to set up a PlaybackState with action */
+ private fun addPlaybackStateAction() {
+ val stateActions = PlaybackState.ACTION_PLAY_PAUSE
+ val stateBuilder = PlaybackState.Builder().setActions(stateActions)
+ stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
index 344dffa..92bf84c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
@@ -72,6 +72,7 @@
private lateinit var executor: FakeExecutor
@Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
@Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit
+ @Mock private lateinit var sessionCallback: (String) -> Unit
@Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
@Captor
private lateinit var dozingCallbackCaptor:
@@ -99,6 +100,7 @@
)
mediaTimeoutListener.timeoutCallback = timeoutCallback
mediaTimeoutListener.stateCallback = stateCallback
+ mediaTimeoutListener.sessionCallback = sessionCallback
// Create a media session and notification for testing.
metadataBuilder =
@@ -284,6 +286,7 @@
verify(mediaController).unregisterCallback(anyObject())
assertThat(executor.numPending()).isEqualTo(0)
verify(logger).logSessionDestroyed(eq(KEY))
+ verify(sessionCallback).invoke(eq(KEY))
}
@Test
@@ -322,6 +325,7 @@
// THEN the controller is unregistered, but the timeout is still scheduled
verify(mediaController).unregisterCallback(anyObject())
assertThat(executor.numPending()).isEqualTo(1)
+ verify(sessionCallback, never()).invoke(eq(KEY))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 094d69a..9a0bd9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -243,6 +243,13 @@
}
@Test
+ public void dismissDialog_closesDialogByBroadcastSender() {
+ mMediaOutputBaseDialogImpl.dismissDialog();
+
+ verify(mBroadcastSender).closeSystemDialogs();
+ }
+
+ @Test
public void whenBroadcasting_verifyLeBroadcastServiceCallBackIsRegisteredAndUnregistered() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index f5432e2..117751c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -252,6 +252,13 @@
}
@Test
+ public void tryToLaunchMediaApplication_nullIntent_skip() {
+ mMediaOutputController.tryToLaunchMediaApplication();
+
+ verify(mCb, never()).dismissDialog();
+ }
+
+ @Test
public void onDevicesUpdated_unregistersNearbyDevicesCallback() throws RemoteException {
mMediaOutputController.start(mCb);
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/shared/regionsampling/RegionSamplerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
index 5a62cc1..ae1c8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
@@ -1,21 +1,17 @@
package com.android.systemui.shared.regionsampling
-import android.graphics.Rect
+import android.app.WallpaperManager
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper
import java.io.PrintWriter
import java.util.concurrent.Executor
-import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -28,9 +24,8 @@
@Mock private lateinit var sampledView: View
@Mock private lateinit var mainExecutor: Executor
@Mock private lateinit var bgExecutor: Executor
- @Mock private lateinit var regionSampler: RegionSamplingHelper
@Mock private lateinit var pw: PrintWriter
- @Mock private lateinit var callback: RegionSamplingHelper.SamplingCallback
+ @Mock private lateinit var wallpaperManager: WallpaperManager
private lateinit var mRegionSampler: RegionSampler
private var updateFun: UpdateColorCallback = {}
@@ -38,65 +33,18 @@
@Before
fun setUp() {
whenever(sampledView.isAttachedToWindow).thenReturn(true)
- whenever(regionSampler.callback).thenReturn(this@RegionSamplerTest.callback)
mRegionSampler =
- object : RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun) {
- override fun createRegionSamplingHelper(
- sampledView: View,
- callback: RegionSamplingHelper.SamplingCallback,
- mainExecutor: Executor?,
- bgExecutor: Executor?
- ): RegionSamplingHelper {
- return this@RegionSamplerTest.regionSampler
- }
- }
+ RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun, wallpaperManager)
}
@Test
fun testStartRegionSampler() {
mRegionSampler.startRegionSampler()
-
- verify(regionSampler).start(Rect(0, 0, 0, 0))
- }
-
- @Test
- fun testStopRegionSampler() {
- mRegionSampler.stopRegionSampler()
-
- verify(regionSampler).stop()
}
@Test
fun testDump() {
mRegionSampler.dump(pw)
-
- verify(regionSampler).dump(pw)
- }
-
- @Test
- fun testUpdateColorCallback() {
- regionSampler.callback.onRegionDarknessChanged(false)
- verify(regionSampler.callback).onRegionDarknessChanged(false)
- clearInvocations(regionSampler.callback)
- regionSampler.callback.onRegionDarknessChanged(true)
- verify(regionSampler.callback).onRegionDarknessChanged(true)
- }
-
- @Test
- fun testFlagFalse() {
- mRegionSampler =
- object : RegionSampler(sampledView, mainExecutor, bgExecutor, false, updateFun) {
- override fun createRegionSamplingHelper(
- sampledView: View,
- callback: RegionSamplingHelper.SamplingCallback,
- mainExecutor: Executor?,
- bgExecutor: Executor?
- ): RegionSamplingHelper {
- return this@RegionSamplerTest.regionSampler
- }
- }
-
- Assert.assertEquals(mRegionSampler.regionSampler, null)
}
}
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/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index be6b1dc..2686238 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -321,7 +321,7 @@
val testDispatcher = UnconfinedTestDispatcher()
val testScope = TestScope(testDispatcher)
val fakeSettings = FakeSettings().apply {
- putBool(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, true)
+ putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
}
val seenNotificationsProvider = SeenNotificationsProviderImpl()
val keyguardCoordinator =
@@ -372,14 +372,14 @@
var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
- fakeSettings.getBoolForUser(
+ fakeSettings.getIntForUser(
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
UserHandle.USER_CURRENT,
- )
+ ) == 1
set(value) {
- fakeSettings.putBoolForUser(
+ fakeSettings.putIntForUser(
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- value,
+ if (value) 1 else 2,
UserHandle.USER_CURRENT,
)
}
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 6fc60f1..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
@@ -147,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());
}
@@ -165,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/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index 1e81dc7..e1668e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -36,7 +36,6 @@
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -79,7 +78,7 @@
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
whenever(inputManager.getInputDevice(0)).thenReturn(btStylusDevice)
whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
- // whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")
+ whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")
stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler)
broadcastReceiver = stylusUsiPowerUi.receiver
@@ -179,7 +178,6 @@
}
@Test
- @Ignore("TODO(b/257936830): get bt address once input api available")
fun refresh_hasConnectedBluetoothStylus_cancelsNotification() {
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0))
@@ -189,7 +187,6 @@
}
@Test
- @Ignore("TODO(b/257936830): get bt address once input api available")
fun refresh_hasConnectedBluetoothStylus_existingNotification_cancelsNotification() {
stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0))
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
index 990db77..f723a9e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
@@ -23,14 +23,20 @@
isUnlocked: Boolean = true,
isShowingAlternateAuthOnUnlock: Boolean = false,
interactionJankMonitor: InteractionJankMonitor = mock(InteractionJankMonitor::class.java),
+ isPredictiveBackQsDialogAnim: Boolean = false,
): DialogLaunchAnimator {
return DialogLaunchAnimator(
- FakeCallback(
- isUnlocked = isUnlocked,
- isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock,
- ),
- interactionJankMonitor,
- fakeLaunchAnimator(),
+ callback =
+ FakeCallback(
+ isUnlocked = isUnlocked,
+ isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock,
+ ),
+ interactionJankMonitor = interactionJankMonitor,
+ featureFlags =
+ object : AnimationFeatureFlags {
+ override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
+ },
+ launchAnimator = fakeLaunchAnimator(),
isForTesting = true,
)
}
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
index b9823c1..077fd2a 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -45,10 +45,13 @@
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<>();
@@ -58,6 +61,12 @@
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,
@@ -133,6 +142,7 @@
@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
@@ -198,8 +208,16 @@
return mCallingAppIcon;
}
- public String getReadableCallerId() {
- return mCallerDisplayName;
+ /**
+ * 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() {
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 47ec80e..2395814c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -176,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();
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 97e9d26..c0bb586 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -156,7 +156,7 @@
"android.hardware.health-V1.0-java", // HIDL
"android.hardware.health-V2.0-java", // HIDL
"android.hardware.health-V2.1-java", // HIDL
- "android.hardware.health-V1-java", // AIDL
+ "android.hardware.health-V2-java", // AIDL
"android.hardware.health-translate-java",
"android.hardware.light-V1-java",
"android.hardware.tv.cec-V1.1-java",
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index a35aa7c..70eeb7f 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -329,11 +329,11 @@
* when the user is first unlocked to update the usage stats package mappings data that might
* be stale or have existed from a restore and belongs to packages that are not installed for
* this user anymore.
- * Note: this is only executed for the system user.
*
+ * @param userId The user to update
* @return {@code true} if the updating was successful, {@code false} otherwise
*/
- public abstract boolean updatePackageMappingsData();
+ public abstract boolean updatePackageMappingsData(@UserIdInt int userId);
/**
* Listener interface for usage events.
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index fce44f5..faa6938 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -62,6 +62,7 @@
import android.os.ShellCommand;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.PackageUtils;
import android.util.Slog;
@@ -131,6 +132,10 @@
// used for indicating newly installed MBAs that are updated (but unused currently)
static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
+ @VisibleForTesting
+ static final String KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION =
+ "enable_biometric_property_verification";
+
private static final boolean DEBUG = false; // toggle this for local debug
private final Context mContext;
@@ -138,6 +143,7 @@
// the system time (in ms) the last measurement was taken
private long mMeasurementsLastRecordedMs;
private PackageManagerInternal mPackageManagerInternal;
+ private BiometricLogger mBiometricLogger;
/**
* Guards whether or not measurements of MBA to be performed. When this change is enabled,
@@ -1049,13 +1055,56 @@
}
private final BinaryTransparencyServiceImpl mServiceImpl;
+ /**
+ * A wrapper of {@link FrameworkStatsLog} for easier testing
+ */
+ @VisibleForTesting
+ public static class BiometricLogger {
+ private static final String TAG = "BiometricLogger";
+
+ private static final BiometricLogger sInstance = new BiometricLogger();
+
+ private BiometricLogger() {}
+
+ public static BiometricLogger getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * A wrapper of {@link FrameworkStatsLog}
+ *
+ * @param sensorId The sensorId of the biometric to be logged
+ * @param modality The modality of the biometric
+ * @param sensorType The sensor type of the biometric
+ * @param sensorStrength The sensor strength of the biometric
+ * @param componentId The component Id of a component of the biometric
+ * @param hardwareVersion The hardware version of a component of the biometric
+ * @param firmwareVersion The firmware version of a component of the biometric
+ * @param serialNumber The serial number of a component of the biometric
+ * @param softwareVersion The software version of a component of the biometric
+ */
+ public void logStats(int sensorId, int modality, int sensorType, int sensorStrength,
+ String componentId, String hardwareVersion, String firmwareVersion,
+ String serialNumber, String softwareVersion) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_PROPERTIES_COLLECTED,
+ sensorId, modality, sensorType, sensorStrength, componentId, hardwareVersion,
+ firmwareVersion, serialNumber, softwareVersion);
+ }
+ }
+
public BinaryTransparencyService(Context context) {
+ this(context, BiometricLogger.getInstance());
+ }
+
+ @VisibleForTesting
+ BinaryTransparencyService(Context context, BiometricLogger biometricLogger) {
super(context);
mContext = context;
mServiceImpl = new BinaryTransparencyServiceImpl();
mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED;
mMeasurementsLastRecordedMs = 0;
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mBiometricLogger = biometricLogger;
}
/**
@@ -1301,7 +1350,7 @@
// Note: none of the component info is a device identifier since every device of a given
// model and build share the same biometric system info (see b/216195167)
for (ComponentInfo componentInfo : prop.getComponentInfo()) {
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_PROPERTIES_COLLECTED,
+ mBiometricLogger.logStats(
sensorId,
modality,
sensorType,
@@ -1314,7 +1363,19 @@
}
}
- private void collectBiometricProperties() {
+ @VisibleForTesting
+ void collectBiometricProperties() {
+ // Check the flag to determine whether biometric property verification is enabled. It's
+ // disabled by default.
+ if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BIOMETRICS,
+ KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, false)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Do not collect/verify biometric properties. Feature disabled by "
+ + "DeviceConfig");
+ }
+ return;
+ }
+
PackageManager pm = mContext.getPackageManager();
FingerprintManager fpManager = null;
FaceManager faceManager = null;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4e5ce88..cfd22e8 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2250,14 +2250,19 @@
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
+ boolean preciseCallStateChanged = false;
mRingingCallState[phoneId] = ringingCallState;
mForegroundCallState[phoneId] = foregroundCallState;
mBackgroundCallState[phoneId] = backgroundCallState;
- mPreciseCallState[phoneId] = new PreciseCallState(
+ PreciseCallState preciseCallState = new PreciseCallState(
ringingCallState, foregroundCallState,
backgroundCallState,
DisconnectCause.NOT_VALID,
PreciseDisconnectCause.NOT_VALID);
+ if (!preciseCallState.equals(mPreciseCallState[phoneId])) {
+ preciseCallStateChanged = true;
+ mPreciseCallState[phoneId] = preciseCallState;
+ }
boolean notifyCallState = true;
if (mCallQuality == null) {
log("notifyPreciseCallState: mCallQuality is null, "
@@ -2271,6 +2276,8 @@
mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mCallQuality[phoneId] = createCallQuality();
}
+ List<CallState> prevCallStateList = new ArrayList<>();
+ prevCallStateList.addAll(mCallStateLists.get(phoneId));
mCallStateLists.get(phoneId).clear();
if (foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
&& foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
@@ -2330,6 +2337,9 @@
}
mCallStateLists.get(phoneId).add(builder.build());
}
+ if (prevCallStateList.equals(mCallStateLists.get(phoneId))) {
+ notifyCallState = false;
+ }
boolean hasOngoingCall = false;
for (CallState cs : mCallStateLists.get(phoneId)) {
if (cs.getCallState() != PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED) {
@@ -2343,23 +2353,30 @@
}
}
- for (Record r : mRecords) {
- if (r.matchTelephonyCallbackEvent(
- TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED)
- && idMatch(r, subId, phoneId)) {
- try {
- r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ if (preciseCallStateChanged) {
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
- if (notifyCallState && r.matchTelephonyCallbackEvent(
- TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
- && idMatch(r, subId, phoneId)) {
- try {
- r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ }
+
+ if (notifyCallState) {
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
@@ -3976,6 +3993,66 @@
}
}
+ /**
+ * Returns a string representation of the radio technology (network type)
+ * currently in use on the device.
+ * @param type for which network type is returned
+ * @return the name of the radio technology
+ *
+ */
+ private String getNetworkTypeName(@Annotation.NetworkType int type) {
+ switch (type) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ return "GPRS";
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ return "EDGE";
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ return "UMTS";
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ return "HSDPA";
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ return "HSUPA";
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ return "HSPA";
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ return "CDMA";
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ return "CDMA - EvDo rev. 0";
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ return "CDMA - EvDo rev. A";
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ return "CDMA - EvDo rev. B";
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ return "CDMA - 1xRTT";
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return "LTE";
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ return "CDMA - eHRPD";
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return "iDEN";
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return "HSPA+";
+ case TelephonyManager.NETWORK_TYPE_GSM:
+ return "GSM";
+ case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
+ return "TD_SCDMA";
+ case TelephonyManager.NETWORK_TYPE_IWLAN:
+ return "IWLAN";
+
+ //TODO: This network type is marked as hidden because it is not a
+ // true network type and we are looking to remove it completely from the available list
+ // of network types. Since this method is only used for logging, in the event that this
+ // network type is selected, the log will read as "Unknown."
+ //case TelephonyManager.NETWORK_TYPE_LTE_CA:
+ // return "LTE_CA";
+
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return "NR";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
/** Returns a new PreciseCallState object with default values. */
private static PreciseCallState createPreciseCallState() {
return new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_NOT_VALID,
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 70f07a9..523e0f0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -951,7 +951,7 @@
"short_fgs_timeout_duration";
/** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
- static final long DEFAULT_SHORT_FGS_TIMEOUT_DURATION = 60_000;
+ static final long DEFAULT_SHORT_FGS_TIMEOUT_DURATION = 3 * 60_000;
/** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
public volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index de87a0c..7d1d0e5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8432,15 +8432,13 @@
t.traceEnd();
}
+ boolean isBootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
+
// Some systems - like automotive - will explicitly unlock system user then switch
- // to a secondary user. Hence, we don't want to send duplicate broadcasts for
- // the system user here.
+ // to a secondary user.
// TODO(b/242195409): this workaround shouldn't be necessary once we move
// the headless-user start logic to UserManager-land.
- final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
- && !UserManager.isHeadlessSystemUserMode();
-
- if (isBootingSystemUser) {
+ if (isBootingSystemUser && !UserManager.isHeadlessSystemUserMode()) {
t.traceBegin("startHomeOnAllDisplays");
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
t.traceEnd();
@@ -8452,6 +8450,10 @@
if (isBootingSystemUser) {
+ // Need to send the broadcasts for the system user here because
+ // UserController#startUserInternal will not send them for the system user starting,
+ // It checks if the user state already exists, which is always the case for the
+ // system user.
t.traceBegin("sendUserStartBroadcast");
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
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 85de637..8d3c21e 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;
@@ -87,7 +88,6 @@
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;
@@ -186,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
@@ -196,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.isMediaSharedWithParent();
+ if (!isAuthorityRedirectedForCloneProfile(name) || !isMediaSharedWithParent) {
// First check if this content provider has been published...
cpr = mProviderMap.getProviderByName(name, userId);
}
@@ -215,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;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 81655cf..114e2c1 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -143,6 +143,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* All of the code required to compute proc states and oom_adj values.
@@ -743,6 +744,45 @@
queue.offer(provider);
provider.mState.setReachable(true);
}
+ // See if this process has any corresponding SDK sandbox processes running, and if so
+ // scan them as well.
+ final List<ProcessRecord> sdkSandboxes =
+ mProcessList.getSdkSandboxProcessesForAppLocked(pr.uid);
+ final int numSdkSandboxes = sdkSandboxes != null ? sdkSandboxes.size() : 0;
+ for (int i = numSdkSandboxes - 1; i >= 0; i--) {
+ ProcessRecord sdkSandbox = sdkSandboxes.get(i);
+ containsCycle |= sdkSandbox.mState.isReachable();
+ if (sdkSandbox.mState.isReachable()) {
+ continue;
+ }
+ queue.offer(sdkSandbox);
+ sdkSandbox.mState.setReachable(true);
+ }
+ // If this process is a sandbox itself, also scan the app on whose behalf its running
+ if (pr.isSdkSandbox) {
+ for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) {
+ ServiceRecord s = psr.getRunningServiceAt(is);
+ ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+ s.getConnections();
+ for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) {
+ ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
+ for (int i = clist.size() - 1; i >= 0; i--) {
+ ConnectionRecord cr = clist.get(i);
+ ProcessRecord attributedApp = cr.binding.attributedClient;
+ if (attributedApp == null || attributedApp == pr
+ || ((attributedApp.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ)
+ && (attributedApp.mState.getMaxAdj() < FOREGROUND_APP_ADJ))) {
+ continue;
+ }
+ if (attributedApp.mState.isReachable()) {
+ continue;
+ }
+ queue.offer(attributedApp);
+ attributedApp.mState.setReachable(true);
+ }
+ }
+ }
+ }
}
int size = processes.size();
@@ -2131,6 +2171,11 @@
boolean trackedProcState = false;
ProcessRecord client = cr.binding.client;
+ if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+ // For SDK sandboxes, use the attributed client (eg the app that
+ // requested the sandbox)
+ client = cr.binding.attributedClient;
+ }
final ProcessStateRecord cstate = client.mState;
if (computeClients) {
computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now,
@@ -2377,12 +2422,12 @@
state.setAdjType(adjType);
state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
.REASON_SERVICE_IN_USE);
- state.setAdjSource(cr.binding.client);
+ state.setAdjSource(client);
state.setAdjSourceProcState(clientProcState);
state.setAdjTarget(s.instanceName);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
- + ": " + app + ", due to " + cr.binding.client
+ + ": " + app + ", due to " + client
+ " adj=" + adj + " procState="
+ ProcessList.makeProcStateString(procState));
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 43075bc..14ecf9f 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -32,6 +32,7 @@
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Overridable;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
@@ -65,6 +66,7 @@
/** If enabled BAL are prevented by default in applications targeting U and later. */
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ @Overridable
private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER = 244637991;
private static final String ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER =
"enable_default_rescind_bal_privileges_from_pending_intent_sender";
@@ -450,16 +452,11 @@
resolvedType = key.requestResolvedType;
}
- // Apply any launch flags from the ActivityOptions. This is used only by SystemUI
- // to ensure that we can launch the pending intent with a consistent launch mode even
- // if the provided PendingIntent is immutable (ie. to force an activity to launch into
- // a new task, or to launch multiple instances if supported by the app)
+ // Apply any launch flags from the ActivityOptions. This is to ensure that the caller
+ // can specify a consistent launch mode even if the PendingIntent is immutable
final ActivityOptions opts = ActivityOptions.fromBundle(options);
if (opts != null) {
- // TODO(b/254490217): Move this check into SafeActivityOptions
- if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) {
- finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
- }
+ finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
}
// Extract options before clearing calling identity
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 3a40fc7..04c0d64 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -485,6 +485,12 @@
final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
/**
+ * The currently running SDK sandbox processes for a uid.
+ */
+ @GuardedBy("mService")
+ final SparseArray<ArrayList<ProcessRecord>> mSdkSandboxes = new SparseArray<>();
+
+ /**
* Managees the {@link android.app.ApplicationExitInfo} records.
*/
@GuardedBy("mAppExitInfoTracker")
@@ -2990,6 +2996,14 @@
if (proc.isolated) {
mIsolatedProcesses.put(proc.uid, proc);
}
+ if (proc.isSdkSandbox) {
+ ArrayList<ProcessRecord> sdkSandboxes = mSdkSandboxes.get(proc.uid);
+ if (sdkSandboxes == null) {
+ sdkSandboxes = new ArrayList<>();
+ }
+ sdkSandboxes.add(proc);
+ mSdkSandboxes.put(Process.getAppUidForSdkSandboxUid(proc.uid), sdkSandboxes);
+ }
}
@GuardedBy("mService")
@@ -3030,6 +3044,19 @@
return ret;
}
+ /**
+ * Returns the associated SDK sandbox processes for a UID. Note that this does
+ * NOT return a copy, so callers should not modify the result, or use it outside
+ * of the lock scope.
+ *
+ * @param uid UID to return sansdbox processes for
+ */
+ @Nullable
+ @GuardedBy("mService")
+ List<ProcessRecord> getSdkSandboxProcessesForAppLocked(int uid) {
+ return mSdkSandboxes.get(uid);
+ }
+
@GuardedBy("mService")
ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
boolean isolated, int isolatedUid, boolean isSdkSandbox, int sdkSandboxUid,
@@ -3135,6 +3162,16 @@
if (record != null && record.appZygote) {
removeProcessFromAppZygoteLocked(record);
}
+ if (record != null && record.isSdkSandbox) {
+ final int appUid = Process.getAppUidForSdkSandboxUid(uid);
+ final ArrayList<ProcessRecord> sdkSandboxesForUid = mSdkSandboxes.get(appUid);
+ if (sdkSandboxesForUid != null) {
+ sdkSandboxesForUid.remove(record);
+ if (sdkSandboxesForUid.size() == 0) {
+ mSdkSandboxes.remove(appUid);
+ }
+ }
+ }
mAppsInBackgroundRestricted.remove(record);
return old;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 67166b8..8ce9889 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1457,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();
}
@@ -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.isCredentialShareableWithParent()) {
return false;
}
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index d195103..893c8b5 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -40,6 +40,7 @@
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.IGameModeListener;
+import android.app.IUidObserver;
import android.app.StatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -165,38 +166,19 @@
private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
@Nullable
private final GameServiceController mGameServiceController;
+ private final Object mUidObserverLock = new Object();
+ @VisibleForTesting
+ @Nullable
+ final UidObserver mUidObserver;
+ @GuardedBy("mUidObserverLock")
+ private final Set<Integer> mForegroundGameUids = new HashSet<>();
public GameManagerService(Context context) {
this(context, createServiceThread().getLooper());
}
GameManagerService(Context context, Looper looper) {
- mContext = context;
- mHandler = new SettingsHandler(looper);
- mPackageManager = mContext.getPackageManager();
- mUserManager = mContext.getSystemService(UserManager.class);
- mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
- mSystemDir = new File(Environment.getDataDirectory(), "system");
- mSystemDir.mkdirs();
- FileUtils.setPermissions(mSystemDir.toString(),
- FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
- -1, -1);
- mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
- GAME_MODE_INTERVENTION_LIST_FILE_NAME));
- FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR
- | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
- -1, -1);
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
- mGameServiceController = new GameServiceController(
- context, BackgroundThread.getExecutor(),
- new GameServiceProviderSelectorImpl(
- context.getResources(),
- context.getPackageManager()),
- new GameServiceProviderInstanceFactoryImpl(context));
- } else {
- mGameServiceController = null;
- }
+ this(context, looper, Environment.getDataDirectory());
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -227,6 +209,14 @@
} else {
mGameServiceController = null;
}
+ mUidObserver = new UidObserver();
+ try {
+ ActivityManager.getService().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not register UidObserver");
+ }
}
@Override
@@ -2152,4 +2142,66 @@
* load dynamic library for frame rate overriding JNI calls
*/
private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
+
+ final class UidObserver extends IUidObserver.Stub {
+ @Override
+ public void onUidIdle(int uid, boolean disabled) {}
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ synchronized (mUidObserverLock) {
+ disableGameMode(uid);
+ }
+ }
+
+ @Override
+ public void onUidActive(int uid) {}
+
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+ synchronized (mUidObserverLock) {
+ if (ActivityManager.isProcStateBackground(procState)) {
+ disableGameMode(uid);
+ return;
+ }
+
+ final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return;
+ }
+
+ final int userId = mContext.getUserId();
+ if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
+ return;
+ }
+
+ if (mForegroundGameUids.isEmpty()) {
+ Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, true);
+ }
+ mForegroundGameUids.add(uid);
+ }
+ }
+
+ private void disableGameMode(int uid) {
+ synchronized (mUidObserverLock) {
+ if (!mForegroundGameUids.contains(uid)) {
+ return;
+ }
+ mForegroundGameUids.remove(uid);
+ if (!mForegroundGameUids.isEmpty()) {
+ return;
+ }
+ Slog.v(TAG,
+ "Game power mode OFF (process remomved or state changed to background)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+ }
+ }
+
+ @Override
+ public void onUidCachedChanged(int uid, boolean cached) {}
+
+ @Override
+ public void onUidProcAdjChanged(int uid) {}
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 92a9f46..5c44cea 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1265,6 +1265,20 @@
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
}
+ private void initVolumeStreamStates() {
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ synchronized (VolumeStreamState.class) {
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ VolumeStreamState streamState = mStreamStates[streamType];
+ int groupId = getVolumeGroupForStreamType(streamType);
+ if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
+ && sVolumeGroupStates.indexOfKey(groupId) >= 0) {
+ streamState.setVolumeGroupState(sVolumeGroupStates.get(groupId));
+ }
+ }
+ }
+ }
+
/**
* Separating notification volume from ring is NOT of aliasing the corresponding streams
* @param properties
@@ -1292,6 +1306,8 @@
initVolumeGroupStates();
mSoundDoseHelper.initSafeUsbMediaVolumeIndex();
+ // Link VGS on VSS
+ initVolumeStreamStates();
// Call setRingerModeInt() to apply correct mute
// state on streams affected by ringer mode.
@@ -2610,13 +2626,11 @@
}
if (!TextUtils.isEmpty(packageName)) {
PackageManager pm = mContext.getPackageManager();
- ActivityManager am =
- (ActivityManager) mContext.getSystemService(mContext.ACTIVITY_SERVICE);
if (pm.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
== PackageManager.PERMISSION_GRANTED) {
try {
- assistantUid = pm.getPackageUidAsUser(packageName, am.getCurrentUser());
+ assistantUid = pm.getPackageUidAsUser(packageName, getCurrentUserId());
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG,
"updateAssistantUId() could not find UID for package: " + packageName);
@@ -3482,15 +3496,7 @@
} else {
state = direction == AudioManager.ADJUST_MUTE;
}
- for (int stream = 0; stream < mStreamStates.length; stream++) {
- if (streamTypeAlias == mStreamVolumeAlias[stream]) {
- if (!(readCameraSoundForced()
- && (mStreamStates[stream].getStreamType()
- == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
- mStreamStates[stream].mute(state);
- }
- }
- }
+ muteAliasStreams(streamTypeAlias, state);
} else if ((direction == AudioManager.ADJUST_RAISE)
&& mSoundDoseHelper.raiseVolumeDisplaySafeMediaVolume(streamTypeAlias,
aliasIndex + step, device, flags)) {
@@ -3505,7 +3511,7 @@
// Unmute the stream if it was previously muted
if (direction == AudioManager.ADJUST_RAISE) {
// unmute immediately for volume up
- streamState.mute(false);
+ muteAliasStreams(streamTypeAlias, false);
} else if (direction == AudioManager.ADJUST_LOWER) {
if (mIsSingleVolume) {
sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
@@ -3631,6 +3637,42 @@
sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device);
}
+ /**
+ * Loops on aliasted stream, update the mute cache attribute of each
+ * {@see AudioService#VolumeStreamState}, and then apply the change.
+ * It prevents to unnecessary {@see AudioSystem#setStreamVolume} done for each stream
+ * and aliases before mute change changed and after.
+ */
+ private void muteAliasStreams(int streamAlias, boolean state) {
+ synchronized (VolumeStreamState.class) {
+ List<Integer> streamsToMute = new ArrayList<>();
+ for (int stream = 0; stream < mStreamStates.length; stream++) {
+ if (streamAlias == mStreamVolumeAlias[stream]) {
+ if (!(readCameraSoundForced()
+ && (mStreamStates[stream].getStreamType()
+ == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
+ boolean changed = mStreamStates[stream].mute(state, /* apply= */ false);
+ if (changed) {
+ streamsToMute.add(stream);
+ }
+ }
+ }
+ }
+ streamsToMute.forEach(streamToMute -> {
+ mStreamStates[streamToMute].doMute();
+ broadcastMuteSetting(streamToMute, state);
+ });
+ }
+ }
+
+ private void broadcastMuteSetting(int streamType, boolean isMuted) {
+ // Stream mute changed, fire the intent.
+ Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
+ intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted);
+ sendBroadcastToAll(intent, null /* options */);
+ }
+
// Called after a delay when volume down is pressed while muted
private void onUnmuteStream(int stream, int flags) {
boolean wasMuted;
@@ -3730,7 +3772,8 @@
// except for BT SCO stream where only explicit mute is allowed to comply to BT requirements
if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO)
&& (getDeviceForStream(stream) == device)) {
- mStreamStates[stream].mute(index == 0);
+ // As adjustStreamVolume with muteAdjust flags mute/unmutes stream and aliased streams.
+ muteAliasStreams(stream, index == 0);
}
}
@@ -3774,23 +3817,23 @@
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */
- public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,
+ @Override
+ @android.annotation.EnforcePermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
+ /** @see AudioManager#setVolumeGroupVolumeIndex(int, int, int) */
+ public void setVolumeGroupVolumeIndex(int groupId, int index, int flags,
String callingPackage, String attributionTag) {
- super.setVolumeIndexForAttributes_enforcePermission();
-
- Objects.requireNonNull(attr, "attr must not be null");
- int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
- attr, /* fallbackOnDefault= */false);
- if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
- Log.e(TAG, ": no volume group found for attributes " + attr.toString());
+ super.setVolumeGroupVolumeIndex_enforcePermission();
+ if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+ Log.e(TAG, ": no volume group found for id " + groupId);
return;
}
- VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+ VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
- sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
- index, flags, callingPackage + ", user " + ActivityManager.getCurrentUser()));
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, vgs.name(),
+ index, flags, callingPackage + ", user " + getCurrentUserId()));
vgs.setVolumeIndex(index, flags);
@@ -3799,7 +3842,7 @@
try {
ensureValidStreamType(groupedStream);
} catch (IllegalArgumentException e) {
- Log.d(TAG, "volume group " + volumeGroup + " has internal streams (" + groupedStream
+ Log.d(TAG, "volume group " + groupId + " has internal streams (" + groupedStream
+ "), do not change associated stream volume");
continue;
}
@@ -3821,39 +3864,55 @@
return null;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- /** @see AudioManager#getVolumeIndexForAttributes(attr) */
- public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
- super.getVolumeIndexForAttributes_enforcePermission();
-
- Objects.requireNonNull(attr, "attr must not be null");
+ @Override
+ @android.annotation.EnforcePermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
+ /** @see AudioManager#getVolumeGroupVolumeIndex(int) */
+ public int getVolumeGroupVolumeIndex(int groupId) {
+ super.getVolumeGroupVolumeIndex_enforcePermission();
synchronized (VolumeStreamState.class) {
- int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
- attr, /* fallbackOnDefault= */false);
- if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
- throw new IllegalArgumentException("No volume group for attributes " + attr);
+ if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+ throw new IllegalArgumentException("No volume group for id " + groupId);
}
- VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
- return vgs.isMuted() ? vgs.getMinIndex() : vgs.getVolumeIndex();
+ VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+ // Return 0 when muted, not min index since for e.g. Voice Call, it has a non zero
+ // min but it mutable on permission condition.
+ return vgs.isMuted() ? 0 : vgs.getVolumeIndex();
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
- public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
- super.getMaxVolumeIndexForAttributes_enforcePermission();
-
- Objects.requireNonNull(attr, "attr must not be null");
- return AudioSystem.getMaxVolumeIndexForAttributes(attr);
+ /** @see AudioManager#getVolumeGroupMaxVolumeIndex(int) */
+ @android.annotation.EnforcePermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
+ public int getVolumeGroupMaxVolumeIndex(int groupId) {
+ super.getVolumeGroupMaxVolumeIndex_enforcePermission();
+ synchronized (VolumeStreamState.class) {
+ if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+ throw new IllegalArgumentException("No volume group for id " + groupId);
+ }
+ VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+ return vgs.getMaxIndex();
+ }
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */
- public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
- super.getMinVolumeIndexForAttributes_enforcePermission();
-
- Objects.requireNonNull(attr, "attr must not be null");
- return AudioSystem.getMinVolumeIndexForAttributes(attr);
+ /** @see AudioManager#getVolumeGroupMinVolumeIndex(int) */
+ @android.annotation.EnforcePermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
+ public int getVolumeGroupMinVolumeIndex(int groupId) {
+ super.getVolumeGroupMinVolumeIndex_enforcePermission();
+ synchronized (VolumeStreamState.class) {
+ if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+ throw new IllegalArgumentException("No volume group for id " + groupId);
+ }
+ VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+ return vgs.getMinIndex();
+ }
}
@Override
@@ -3922,6 +3981,71 @@
callingPackage, /*attributionTag*/ null);
}
+ /** @see AudioManager#adjustVolumeGroupVolume(int, int, int) */
+ public void adjustVolumeGroupVolume(int groupId, int direction, int flags,
+ String callingPackage) {
+ ensureValidDirection(direction);
+ if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+ Log.e(TAG, ": no volume group found for id " + groupId);
+ return;
+ }
+ VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+ // For compatibility reason, use stream API if group linked to a valid stream
+ boolean fallbackOnStream = false;
+ for (int stream : vgs.getLegacyStreamTypes()) {
+ try {
+ ensureValidStreamType(stream);
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "volume group " + groupId + " has internal streams (" + stream
+ + "), do not change associated stream volume");
+ continue;
+ }
+ // Note: Group and Stream does not share same convention, 0 is mute for stream,
+ // min index is acting as mute for Groups
+ if (vgs.isVssMuteBijective(stream)) {
+ adjustStreamVolume(stream, direction, flags, callingPackage);
+ if (isMuteAdjust(direction)) {
+ // will be propagated to all aliased streams
+ return;
+ }
+ fallbackOnStream = true;
+ }
+ }
+ if (fallbackOnStream) {
+ // Handled by at least one stream, will be propagated to group, bailing out.
+ return;
+ }
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(),
+ direction, flags, callingPackage));
+ vgs.adjustVolume(direction, flags);
+ }
+
+ /** @see AudioManager#getLastAudibleVolumeGroupVolume(int) */
+ @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE)
+ public int getLastAudibleVolumeGroupVolume(int groupId) {
+ super.getLastAudibleVolumeGroupVolume_enforcePermission();
+ synchronized (VolumeStreamState.class) {
+ if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+ Log.e(TAG, ": no volume group found for id " + groupId);
+ return 0;
+ }
+ VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+ return vgs.getVolumeIndex();
+ }
+ }
+
+ /** @see AudioManager#isVolumeGroupMuted(int) */
+ public boolean isVolumeGroupMuted(int groupId) {
+ synchronized (VolumeStreamState.class) {
+ if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+ Log.e(TAG, ": no volume group found for id " + groupId);
+ return false;
+ }
+ VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+ return vgs.isMuted();
+ }
+ }
+
/** @see AudioManager#setStreamVolume(int, int, int)
* Part of service interface, check permissions here */
public void setStreamVolumeWithAttribution(int streamType, int index, int flags,
@@ -5110,7 +5234,7 @@
}
private void setRingerMode(int ringerMode, String caller, boolean external) {
- if (mUseFixedVolume || mIsSingleVolume) {
+ if (mUseFixedVolume || mIsSingleVolume || mUseVolumeGroupAliases) {
return;
}
if (caller == null || caller.length() == 0) {
@@ -7413,6 +7537,16 @@
|| stream == AudioSystem.STREAM_BLUETOOTH_SCO;
}
+ private static int getVolumeGroupForStreamType(int stream) {
+ AudioAttributes attributes =
+ AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(stream);
+ if (attributes.equals(new AudioAttributes.Builder().build())) {
+ return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+ }
+ return AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+ attributes, /* fallbackOnDefault= */ false);
+ }
+
// NOTE: Locking order for synchronized objects related to volume management:
// 1 mSettingsLock
// 2 VolumeStreamState.class
@@ -7521,14 +7655,62 @@
return mIsMuted;
}
+ public void adjustVolume(int direction, int flags) {
+ synchronized (AudioService.VolumeStreamState.class) {
+ int device = getDeviceForVolume();
+ int previousIndex = getIndex(device);
+ if (isMuteAdjust(direction) && !isMutable()) {
+ // Non mutable volume group
+ if (DEBUG_VOL) {
+ Log.d(TAG, "invalid mute on unmutable volume group " + name());
+ }
+ return;
+ }
+ switch (direction) {
+ case AudioManager.ADJUST_TOGGLE_MUTE: {
+ // Note: If muted by volume 0, unmute will restore volume 0.
+ mute(!mIsMuted);
+ break;
+ }
+ case AudioManager.ADJUST_UNMUTE:
+ // Note: If muted by volume 0, unmute will restore volume 0.
+ mute(false);
+ break;
+ case AudioManager.ADJUST_MUTE:
+ // May be already muted by setvolume 0, prevent from setting same value
+ if (previousIndex != 0) {
+ // bypass persist
+ mute(true);
+ }
+ mIsMuted = true;
+ break;
+ case AudioManager.ADJUST_RAISE:
+ // As for stream, RAISE during mute will increment the index
+ setVolumeIndex(Math.min(previousIndex + 1, mIndexMax), device, flags);
+ break;
+ case AudioManager.ADJUST_LOWER:
+ // For stream, ADJUST_LOWER on a muted VSS is a no-op
+ // If we decide to unmute on ADJUST_LOWER, cannot fallback on
+ // adjustStreamVolume for group associated to legacy stream type
+ if (isMuted() && previousIndex != 0) {
+ mute(false);
+ } else {
+ int newIndex = Math.max(previousIndex - 1, mIndexMin);
+ setVolumeIndex(newIndex, device, flags);
+ }
+ break;
+ }
+ }
+ }
+
public int getVolumeIndex() {
- synchronized (VolumeStreamState.class) {
+ synchronized (AudioService.VolumeStreamState.class) {
return getIndex(getDeviceForVolume());
}
}
public void setVolumeIndex(int index, int flags) {
- synchronized (VolumeStreamState.class) {
+ synchronized (AudioService.VolumeStreamState.class) {
if (mUseFixedVolume) {
return;
}
@@ -7613,7 +7795,7 @@
public void applyAllVolumes(boolean userSwitch) {
String caller = "from vgs";
- synchronized (VolumeStreamState.class) {
+ synchronized (AudioService.VolumeStreamState.class) {
// apply device specific volumes first
for (int i = 0; i < mIndexMap.size(); i++) {
int device = mIndexMap.keyAt(i);
@@ -7713,7 +7895,7 @@
Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+ mAudioVolumeGroup.name()
+ ", device " + AudioSystem.getOutputDeviceName(device)
- + " and User=" + ActivityManager.getCurrentUser());
+ + " and User=" + getCurrentUserId());
}
boolean success = mSettings.putSystemIntForUser(mContentResolver,
getSettingNameForDevice(device),
@@ -7725,7 +7907,7 @@
}
public void readSettings() {
- synchronized (VolumeStreamState.class) {
+ synchronized (AudioService.VolumeStreamState.class) {
// force maximum volume on all streams if fixed volume property is set
if (mUseFixedVolume) {
mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
@@ -7752,7 +7934,7 @@
if (DEBUG_VOL) {
Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+ " for group " + mAudioVolumeGroup.name() + ", device: " + name
- + ", User=" + ActivityManager.getCurrentUser());
+ + ", User=" + getCurrentUserId());
}
mIndexMap.put(device, getValidIndex(index));
}
@@ -7829,13 +8011,14 @@
// 4 VolumeStreamState.class
/*package*/ class VolumeStreamState {
private final int mStreamType;
+ private VolumeGroupState mVolumeGroupState = null;
private int mIndexMin;
// min index when user doesn't have permission to change audio settings
private int mIndexMinNoPerm;
private int mIndexMax;
- private boolean mIsMuted;
- private boolean mIsMutedInternally;
+ private boolean mIsMuted = false;
+ private boolean mIsMutedInternally = false;
private String mVolumeIndexSettingName;
@NonNull private Set<Integer> mObservedDeviceSet = new TreeSet<>();
@@ -7908,6 +8091,15 @@
}
/**
+ * Associate a {@link volumeGroupState} on the {@link VolumeStreamState}.
+ * <p> It helps to synchronize the index, mute attributes on the maching
+ * {@link volumeGroupState}
+ * @param volumeGroupState matching the {@link VolumeStreamState}
+ */
+ public void setVolumeGroupState(VolumeGroupState volumeGroupState) {
+ mVolumeGroupState = volumeGroupState;
+ }
+ /**
* Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission
* @param index minimum index expressed in "UI units", i.e. no 10x factor
*/
@@ -8168,6 +8360,9 @@
}
}
if (changed) {
+ // If associated to volume group, update group cache
+ updateVolumeGroupIndex(device, /* forceMuteState= */ false);
+
oldIndex = (oldIndex + 5) / 10;
index = (index + 5) / 10;
// log base stream changes to the event log
@@ -8285,6 +8480,28 @@
}
}
+ // If associated to volume group, update group cache
+ private void updateVolumeGroupIndex(int device, boolean forceMuteState) {
+ synchronized (VolumeStreamState.class) {
+ if (mVolumeGroupState != null) {
+ int groupIndex = (getIndex(device) + 5) / 10;
+ if (DEBUG_VOL) {
+ Log.d(TAG, "updateVolumeGroupIndex for stream " + mStreamType
+ + ", muted=" + mIsMuted + ", device=" + device + ", index="
+ + getIndex(device) + ", group " + mVolumeGroupState.name()
+ + " Muted=" + mVolumeGroupState.isMuted() + ", Index=" + groupIndex
+ + ", forceMuteState=" + forceMuteState);
+ }
+ mVolumeGroupState.updateVolumeIndex(groupIndex, device);
+ // Only propage mute of stream when applicable
+ if (mIndexMin == 0 || isCallStream(mStreamType)) {
+ // For call stream, align mute only when muted, not when index is set to 0
+ mVolumeGroupState.mute(forceMuteState ? mIsMuted : groupIndex == 0);
+ }
+ }
+ }
+ }
+
/**
* Mute/unmute the stream
* @param state the new mute state
@@ -8293,27 +8510,10 @@
public boolean mute(boolean state) {
boolean changed = false;
synchronized (VolumeStreamState.class) {
- if (state != mIsMuted) {
- changed = true;
- mIsMuted = state;
-
- // Set the new mute volume. This propagates the values to
- // the audio system, otherwise the volume won't be changed
- // at the lower level.
- sendMsg(mAudioHandler,
- MSG_SET_ALL_VOLUMES,
- SENDMSG_QUEUE,
- 0,
- 0,
- this, 0);
- }
+ changed = mute(state, true);
}
if (changed) {
- // Stream mute changed, fire the intent.
- Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
- intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
- intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
- sendBroadcastToAll(intent, null /* options */);
+ broadcastMuteSetting(mStreamType, state);
}
return changed;
}
@@ -8345,6 +8545,44 @@
return mIsMuted || mIsMutedInternally;
}
+ /**
+ * Mute/unmute the stream
+ * @param state the new mute state
+ * @param apply true to propagate to HW, or false just to update the cache. May be needed
+ * to mute a stream and its aliases as applyAllVolume will force settings to aliases.
+ * It prevents unnecessary calls to {@see AudioSystem#setStreamVolume}
+ * @return true if the mute state was changed
+ */
+ public boolean mute(boolean state, boolean apply) {
+ synchronized (VolumeStreamState.class) {
+ boolean changed = state != mIsMuted;
+ if (changed) {
+ mIsMuted = state;
+ if (apply) {
+ doMute();
+ }
+ }
+ return changed;
+ }
+ }
+
+ public void doMute() {
+ synchronized (VolumeStreamState.class) {
+ // If associated to volume group, update group cache
+ updateVolumeGroupIndex(getDeviceForStream(mStreamType), /* forceMuteState= */ true);
+
+ // Set the new mute volume. This propagates the values to
+ // the audio system, otherwise the volume won't be changed
+ // at the lower level.
+ sendMsg(mAudioHandler,
+ MSG_SET_ALL_VOLUMES,
+ SENDMSG_QUEUE,
+ 0,
+ 0,
+ this, 0);
+ }
+ }
+
public int getStreamType() {
return mStreamType;
}
@@ -8414,6 +8652,9 @@
pw.println();
pw.print(" Devices: ");
pw.print(AudioSystem.deviceSetToString(getDeviceSetForStream(mStreamType)));
+ pw.println();
+ pw.print(" Volume Group: ");
+ pw.println(mVolumeGroupState != null ? mVolumeGroupState.name() : "n/a");
}
}
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index d30bec7..58caf5a 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -17,7 +17,6 @@
package com.android.server.audio;
import android.annotation.NonNull;
-import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -197,6 +196,7 @@
static final int VOL_SET_GROUP_VOL = 8;
static final int VOL_MUTE_STREAM_INT = 9;
static final int VOL_SET_LE_AUDIO_VOL = 10;
+ static final int VOL_ADJUST_GROUP_VOL = 11;
final int mOp;
final int mStream;
@@ -204,7 +204,6 @@
final int mVal2;
final String mCaller;
final String mGroupName;
- final AudioAttributes mAudioAttributes;
/** used for VOL_ADJUST_VOL_UID,
* VOL_ADJUST_SUGG_VOL,
@@ -217,7 +216,6 @@
mVal2 = val2;
mCaller = caller;
mGroupName = null;
- mAudioAttributes = null;
logMetricEvent();
}
@@ -230,7 +228,6 @@
mStream = -1;
mCaller = null;
mGroupName = null;
- mAudioAttributes = null;
logMetricEvent();
}
@@ -243,7 +240,6 @@
mStream = -1;
mCaller = null;
mGroupName = null;
- mAudioAttributes = null;
logMetricEvent();
}
@@ -256,7 +252,6 @@
// unused
mCaller = null;
mGroupName = null;
- mAudioAttributes = null;
logMetricEvent();
}
@@ -269,19 +264,18 @@
// unused
mCaller = null;
mGroupName = null;
- mAudioAttributes = null;
logMetricEvent();
}
- /** used for VOL_SET_GROUP_VOL */
- VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
+ /** used for VOL_SET_GROUP_VOL,
+ * VOL_ADJUST_GROUP_VOL */
+ VolumeEvent(int op, String group, int index, int flags, String caller) {
mOp = op;
mStream = -1;
mVal1 = index;
mVal2 = flags;
mCaller = caller;
mGroupName = group;
- mAudioAttributes = aa;
logMetricEvent();
}
@@ -293,7 +287,6 @@
mVal2 = 0;
mCaller = null;
mGroupName = null;
- mAudioAttributes = null;
logMetricEvent();
}
@@ -335,6 +328,15 @@
.record();
return;
}
+ case VOL_ADJUST_GROUP_VOL:
+ new MediaMetrics.Item(mMetricsId)
+ .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
+ .set(MediaMetrics.Property.DIRECTION, mVal1 > 0 ? "up" : "down")
+ .set(MediaMetrics.Property.EVENT, "adjustVolumeGroupVolume")
+ .set(MediaMetrics.Property.FLAGS, mVal2)
+ .set(MediaMetrics.Property.GROUP, mGroupName)
+ .record();
+ return;
case VOL_SET_STREAM_VOL:
new MediaMetrics.Item(mMetricsId)
.set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
@@ -386,7 +388,6 @@
return;
case VOL_SET_GROUP_VOL:
new MediaMetrics.Item(mMetricsId)
- .set(MediaMetrics.Property.ATTRIBUTES, mAudioAttributes.toString())
.set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
.set(MediaMetrics.Property.EVENT, "setVolumeIndexForAttributes")
.set(MediaMetrics.Property.FLAGS, mVal2)
@@ -412,6 +413,13 @@
.append(" flags:0x").append(Integer.toHexString(mVal2))
.append(") from ").append(mCaller)
.toString();
+ case VOL_ADJUST_GROUP_VOL:
+ return new StringBuilder("adjustVolumeGroupVolume(group:")
+ .append(mGroupName)
+ .append(" dir:").append(AudioManager.adjustToString(mVal1))
+ .append(" flags:0x").append(Integer.toHexString(mVal2))
+ .append(") from ").append(mCaller)
+ .toString();
case VOL_ADJUST_STREAM_VOL:
return new StringBuilder("adjustStreamVolume(stream:")
.append(AudioSystem.streamToString(mStream))
@@ -460,8 +468,7 @@
.append(" stream:").append(AudioSystem.streamToString(mStream))
.toString();
case VOL_SET_GROUP_VOL:
- return new StringBuilder("setVolumeIndexForAttributes(attr:")
- .append(mAudioAttributes.toString())
+ return new StringBuilder("setVolumeIndexForAttributes(group:")
.append(" group: ").append(mGroupName)
.append(" index:").append(mVal1)
.append(" flags:0x").append(Integer.toHexString(mVal2))
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index b18be3c..08dcccf 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -86,7 +86,7 @@
private static final Set<String> sEligibleForMultiUser = Sets.newArraySet(
PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER, APP_LOCALES_HELPER,
- ACCOUNT_MANAGER_HELPER);
+ ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER);
private int mUserId = UserHandle.USER_SYSTEM;
@@ -100,7 +100,7 @@
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(mUserId));
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
- addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
+ addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(mUserId));
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper(mUserId));
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
diff --git a/services/core/java/com/android/server/backup/UsageStatsBackupHelper.java b/services/core/java/com/android/server/backup/UsageStatsBackupHelper.java
index d6a70d3..4098c1a 100644
--- a/services/core/java/com/android/server/backup/UsageStatsBackupHelper.java
+++ b/services/core/java/com/android/server/backup/UsageStatsBackupHelper.java
@@ -1,9 +1,9 @@
package com.android.server.backup;
+import android.annotation.UserIdInt;
import android.app.backup.BlobBackupHelper;
import android.app.usage.UsageStatsManagerInternal;
-import android.content.Context;
import android.os.UserHandle;
import android.util.Log;
@@ -26,8 +26,16 @@
// same as UsageStatsDatabase.KEY_USAGE_STATS
static final String KEY_USAGE_STATS = "usage_stats";
- public UsageStatsBackupHelper(Context context) {
+ private final @UserIdInt int mUserId;
+
+ /**
+ * Marshall/unmarshall the usagestats data for the given user
+ *
+ * @param userId The userId to backup/restore
+ */
+ public UsageStatsBackupHelper(@UserIdInt int userId) {
super(BLOB_VERSION, KEY_USAGE_STATS);
+ mUserId = userId;
}
@Override
@@ -38,8 +46,11 @@
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
try {
+ // Note: Write 0 here deliberately so that a backup from a secondary user
+ // can still be restored to an older OS where the restore was always to user 0
+ // Writing the actual userId here would result in restores not working on pre-U.
out.writeInt(UserHandle.USER_SYSTEM);
- out.write(localUsageStatsManager.getBackupPayload(UserHandle.USER_SYSTEM, key));
+ out.write(localUsageStatsManager.getBackupPayload(mUserId, key));
} catch (IOException ioe) {
if (DEBUG) Log.e(TAG, "Failed to backup Usage Stats", ioe);
baos.reset();
@@ -49,7 +60,6 @@
return null;
}
-
@Override
protected void applyRestoredPayload(String key, byte[] payload) {
if (KEY_USAGE_STATS.equals(key)) {
@@ -57,10 +67,10 @@
LocalServices.getService(UsageStatsManagerInternal.class);
DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload));
try {
- int user = in.readInt();
+ in.readInt(); // Legacy userId parameter, read and ignore
byte[] restoreData = new byte[payload.length - 4];
in.read(restoreData, 0, restoreData.length);
- localUsageStatsManager.applyRestoredPayload(user, key, restoreData);
+ localUsageStatsManager.applyRestoredPayload(mUserId, key, restoreData);
} catch (IOException ioe) {
if (DEBUG) Log.e(TAG, "Failed to restore Usage Stats", ioe);
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d558e69..466070f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -109,6 +109,7 @@
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;
@@ -260,6 +261,13 @@
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>();
@@ -1640,7 +1648,16 @@
DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
- dpc.onDisplayChanged();
+ 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);
}
}
@@ -1698,7 +1715,15 @@
final int displayId = display.getDisplayIdLocked();
final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
- dpc.onDisplayChanged();
+ 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);
}
}
@@ -2651,6 +2676,26 @@
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;
+ }
+
+ 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) {
@@ -2666,17 +2711,23 @@
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));
+ () -> handleBrightnessChange(display), hbmMetadata);
} else {
displayPowerController = new DisplayPowerController(
mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display));
+ () -> handleBrightnessChange(display), hbmMetadata);
}
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index c960416..f8d6c5f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -3081,10 +3081,10 @@
@Override
public boolean supportsFrameRateOverride() {
- return SurfaceFlingerProperties.enable_frame_rate_override().orElse(true)
+ return SurfaceFlingerProperties.enable_frame_rate_override().orElse(false)
&& !SurfaceFlingerProperties.frame_rate_override_for_native_rates()
- .orElse(false)
- && SurfaceFlingerProperties.frame_rate_override_global().orElse(true);
+ .orElse(true)
+ && SurfaceFlingerProperties.frame_rate_override_global().orElse(false);
}
private DisplayManager getDisplayManager() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index cdaa3d0..142ec68 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -388,6 +388,7 @@
private float[] mNitsRange;
private final HighBrightnessModeController mHbmController;
+ private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
private final BrightnessThrottler mBrightnessThrottler;
@@ -505,13 +506,14 @@
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
- Runnable onBrightnessChangeRunnable) {
+ Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) {
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);
@@ -790,7 +792,7 @@
* Make sure DisplayManagerService.mSyncRoot is held when this is called
*/
@Override
- public void onDisplayChanged() {
+ public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata) {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
Slog.wtf(mTag, "Display Device is null in DisplayPowerController for display: "
@@ -812,7 +814,7 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
- loadFromDisplayDeviceConfig(token, info);
+ loadFromDisplayDeviceConfig(token, info, hbmMetadata);
/// 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
@@ -864,7 +866,8 @@
}
}
- private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
+ private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
+ HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -877,6 +880,7 @@
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
+ mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
mDisplayDeviceConfig.getHighBrightnessModeData(),
new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
@@ -1961,7 +1965,7 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.update();
}
- }, mContext);
+ }, mHighBrightnessModeMetadata, 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 df0e2d3..ba9fe38 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -327,6 +327,7 @@
private float[] mNitsRange;
private final HighBrightnessModeController mHbmController;
+ private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
private final BrightnessThrottler mBrightnessThrottler;
@@ -415,7 +416,7 @@
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
- Runnable onBrightnessChangeRunnable) {
+ Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) {
mInjector = injector != null ? injector : new Injector();
mClock = mInjector.getClock();
@@ -431,6 +432,7 @@
mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
() -> updatePowerState(), mDisplayId, mSensorManager);
+ mHighBrightnessModeMetadata = hbmMetadata;
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
@@ -681,7 +683,7 @@
* Make sure DisplayManagerService.mSyncRoot lock is held when this is called
*/
@Override
- public void onDisplayChanged() {
+ public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata) {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
@@ -695,6 +697,7 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
+
mHandler.post(() -> {
boolean changed = false;
if (mDisplayDevice != device) {
@@ -703,7 +706,7 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
- loadFromDisplayDeviceConfig(token, info);
+ loadFromDisplayDeviceConfig(token, info, hbmMetadata);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
// Since the underlying display-device changed, we really don't know the
@@ -750,7 +753,8 @@
}
}
- private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
+ private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
+ HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -762,6 +766,7 @@
mBrightnessRampIncreaseMaxTimeMillis,
mBrightnessRampDecreaseMaxTimeMillis);
}
+ mHbmController.setHighBrightnessModeMetadata(hbmMetadata);
mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
mDisplayDeviceConfig.getHighBrightnessModeData(),
new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
@@ -1718,7 +1723,7 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.update();
}
- }, mContext);
+ }, mHighBrightnessModeMetadata, mContext);
}
private BrightnessThrottler createBrightnessThrottlerLocked() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index e750ee2..7b019846 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -31,10 +31,14 @@
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();
+ void onDisplayChanged(HighBrightnessModeMetadata hbmInfo);
/**
* 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
new file mode 100644
index 0000000..5675e2f
--- /dev/null
+++ b/services/core/java/com/android/server/display/HbmEvent.java
@@ -0,0 +1,46 @@
+/*
+ * 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 f98c7df..2c843a4 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -105,30 +105,23 @@
private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
/**
- * If HBM is currently running, this is the start time for the current HBM session.
+ * If HBM is currently running, this is the start time and set of all events,
+ * for the current HBM session.
*/
- 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<>();
-
+ private HighBrightnessModeMetadata mHighBrightnessModeMetadata = null;
HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
String displayUniqueId, float brightnessMin, float brightnessMax,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
- Runnable hbmChangeCallback, Context context) {
+ Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
- brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, context);
+ brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, 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, Context context) {
+ Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
mInjector = injector;
mContext = context;
mClock = injector.getClock();
@@ -137,6 +130,7 @@
mBrightnessMin = brightnessMin;
mBrightnessMax = brightnessMax;
mHbmChangeCallback = hbmChangeCallback;
+ mHighBrightnessModeMetadata = hbmMetadata;
mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
mRecalcRunnable = this::recalculateTimeAllowance;
@@ -222,19 +216,22 @@
// If we are starting or ending a high brightness mode session, store the current
// session in mRunningStartTimeMillis, or the old one in mEvents.
- final boolean wasHbmDrainingAvailableTime = mRunningStartTimeMillis != -1;
+ final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
+ final boolean wasHbmDrainingAvailableTime = runningStartTime != -1;
final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint
&& !mIsHdrLayerPresent;
if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
final long currentTime = mClock.uptimeMillis();
if (shouldHbmDrainAvailableTime) {
- mRunningStartTimeMillis = currentTime;
+ mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
} else {
- mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime));
- mRunningStartTimeMillis = -1;
+ final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime);
+ mHighBrightnessModeMetadata.addHbmEvent(hbmEvent);
+ mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1);
if (DEBUG) {
- Slog.d(TAG, "New HBM event: " + mEvents.peekFirst());
+ Slog.d(TAG, "New HBM event: "
+ + mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst());
}
}
}
@@ -260,6 +257,10 @@
mSettingsObserver.stopObserving();
}
+ void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) {
+ mHighBrightnessModeMetadata = hbmInfo;
+ }
+
void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) {
mWidth = width;
@@ -316,20 +317,22 @@
pw.println(" mBrightnessMax=" + mBrightnessMax);
pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
pw.println(" mIsTimeAvailable= " + mIsTimeAvailable);
- pw.println(" mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis));
+ pw.println(" mRunningStartTimeMillis="
+ + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
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;
- if (mRunningStartTimeMillis != -1) {
- lastStartTime = dumpHbmEvent(pw, new HbmEvent(mRunningStartTimeMillis, currentTime));
+ long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
+ if (runningStartTimeMillis != -1) {
+ lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime));
}
- for (HbmEvent event : mEvents) {
- if (lastStartTime > event.endTimeMillis) {
+ for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) {
+ if (lastStartTime > event.getEndTimeMillis()) {
pw.println(" event: [normal brightness]: "
- + TimeUtils.formatDuration(lastStartTime - event.endTimeMillis));
+ + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis()));
}
lastStartTime = dumpHbmEvent(pw, event);
}
@@ -338,12 +341,12 @@
}
private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
- final long duration = event.endTimeMillis - event.startTimeMillis;
+ final long duration = event.getEndTimeMillis() - event.getStartTimeMillis();
pw.println(" event: ["
- + TimeUtils.formatUptime(event.startTimeMillis) + ", "
- + TimeUtils.formatUptime(event.endTimeMillis) + "] ("
+ + TimeUtils.formatUptime(event.getStartTimeMillis()) + ", "
+ + TimeUtils.formatUptime(event.getEndTimeMillis()) + "] ("
+ TimeUtils.formatDuration(duration) + ")");
- return event.startTimeMillis;
+ return event.getStartTimeMillis();
}
private boolean isCurrentlyAllowed() {
@@ -372,13 +375,15 @@
// First, lets see how much time we've taken for any currently running
// session of HBM.
- if (mRunningStartTimeMillis > 0) {
- if (mRunningStartTimeMillis > currentTime) {
+ long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
+ if (runningStartTimeMillis > 0) {
+ if (runningStartTimeMillis > currentTime) {
Slog.e(TAG, "Start time set to the future. curr: " + currentTime
- + ", start: " + mRunningStartTimeMillis);
- mRunningStartTimeMillis = currentTime;
+ + ", start: " + runningStartTimeMillis);
+ mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
+ runningStartTimeMillis = currentTime;
}
- timeAlreadyUsed = currentTime - mRunningStartTimeMillis;
+ timeAlreadyUsed = currentTime - runningStartTimeMillis;
}
if (DEBUG) {
@@ -387,18 +392,19 @@
// Next, lets iterate through the history of previous sessions and add those times.
final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
- Iterator<HbmEvent> it = mEvents.iterator();
+ Iterator<HbmEvent> it = mHighBrightnessModeMetadata.getHbmEventQueue().iterator();
while (it.hasNext()) {
final HbmEvent event = it.next();
// If this event ended before the current Timing window, discard forever and ever.
- if (event.endTimeMillis < windowstartTimeMillis) {
+ if (event.getEndTimeMillis() < windowstartTimeMillis) {
it.remove();
continue;
}
- final long startTimeMillis = Math.max(event.startTimeMillis, windowstartTimeMillis);
- timeAlreadyUsed += event.endTimeMillis - startTimeMillis;
+ final long startTimeMillis = Math.max(event.getStartTimeMillis(),
+ windowstartTimeMillis);
+ timeAlreadyUsed += event.getEndTimeMillis() - startTimeMillis;
}
if (DEBUG) {
@@ -425,17 +431,18 @@
// 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 && mEvents.size() > 0) {
+ } else if (!mIsTimeAvailable && hbmEvents.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 = mEvents.peekLast();
+ final HbmEvent lastEvent = hbmEvents.peekLast();
final long startTimePlusMinMillis =
- Math.max(windowstartTimeMillis, lastEvent.startTimeMillis)
+ Math.max(windowstartTimeMillis, lastEvent.getStartTimeMillis())
+ mHbmData.timeMinMillis;
final long timeWhenMinIsGainedBack =
currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime;
@@ -459,9 +466,10 @@
+ ", mUnthrottledBrightness: " + mUnthrottledBrightness
+ ", mThrottlingReason: "
+ BrightnessInfo.briMaxReasonToString(mThrottlingReason)
- + ", RunningStartTimeMillis: " + mRunningStartTimeMillis
+ + ", RunningStartTimeMillis: "
+ + mHighBrightnessModeMetadata.getRunningStartTimeMillis()
+ ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
- + ", events: " + mEvents);
+ + ", events: " + hbmEvents);
}
if (nextTimeout != -1) {
@@ -588,25 +596,6 @@
}
}
- /**
- * 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
new file mode 100644
index 0000000..37234ff
--- /dev/null
+++ b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java
@@ -0,0 +1,58 @@
+/*
+ * 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/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index ceb8785..96faee2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -1096,15 +1096,15 @@
HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.length];
int i = 0;
for (android.hardware.tv.hdmi.connection.HdmiPortInfo portInfo : hdmiPortInfos) {
- hdmiPortInfo[i] =
- new HdmiPortInfo(
+ hdmiPortInfo[i] = new HdmiPortInfo.Builder(
portInfo.portId,
portInfo.type,
- portInfo.physicalAddress,
- portInfo.cecSupported,
- false,
- portInfo.arcSupported,
- false);
+ portInfo.physicalAddress)
+ .setCecSupported(portInfo.cecSupported)
+ .setMhlSupported(false)
+ .setArcSupported(portInfo.arcSupported)
+ .setEarcSupported(false)
+ .build();
i++;
}
return hdmiPortInfo;
@@ -1260,13 +1260,15 @@
HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
int i = 0;
for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
- hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId,
+ hdmiPortInfo[i] = new HdmiPortInfo.Builder(
+ portInfo.portId,
portInfo.type,
- portInfo.physicalAddress,
- portInfo.cecSupported,
- false,
- portInfo.arcSupported,
- false);
+ portInfo.physicalAddress)
+ .setCecSupported(portInfo.cecSupported)
+ .setMhlSupported(false)
+ .setArcSupported(portInfo.arcSupported)
+ .setEarcSupported(false)
+ .build();
i++;
}
return hdmiPortInfo;
@@ -1442,13 +1444,15 @@
HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
int i = 0;
for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
- hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId,
+ hdmiPortInfo[i] = new HdmiPortInfo.Builder(
+ portInfo.portId,
portInfo.type,
- portInfo.physicalAddress,
- portInfo.cecSupported,
- false,
- portInfo.arcSupported,
- false);
+ portInfo.physicalAddress)
+ .setCecSupported(portInfo.cecSupported)
+ .setMhlSupported(false)
+ .setArcSupported(portInfo.arcSupported)
+ .setEarcSupported(false)
+ .build();
i++;
}
return hdmiPortInfo;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index a57292a..18a69c8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -469,9 +469,12 @@
ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
for (HdmiPortInfo info : cecPortInfo) {
if (mhlSupportedPorts.contains(info.getId())) {
- result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
- info.isCecSupported(), true, info.isArcSupported(),
- info.isEarcSupported()));
+ result.add(new HdmiPortInfo.Builder(info.getId(), info.getType(), info.getAddress())
+ .setCecSupported(info.isCecSupported())
+ .setMhlSupported(true)
+ .setArcSupported(info.isArcSupported())
+ .setEarcSupported(info.isEarcSupported())
+ .build());
} else {
result.add(info);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 31b8ef2..48e0c30 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -114,6 +114,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -2503,6 +2504,28 @@
return mRecoverableKeyStoreManager.getKey(alias);
}
+ /**
+ * Starts a session to verify lock screen credentials provided by a remote device.
+ */
+ public void startRemoteLockscreenValidation() {
+ if (!FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) {
+ throw new UnsupportedOperationException("Under development");
+ }
+ mRecoverableKeyStoreManager.startRemoteLockscreenValidation();
+ }
+
+ /**
+ * Verifies credentials guess from a remote device.
+ */
+ public void validateRemoteLockscreen(@NonNull byte[] encryptedCredential) {
+ if (!FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) {
+ throw new UnsupportedOperationException("Under development");
+ }
+ mRecoverableKeyStoreManager.validateRemoteLockscreen(encryptedCredential);
+ }
+
// Reading these settings needs the contacts permission
private static final String[] READ_CONTACTS_PROTECTED_SETTINGS = new String[] {
Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index e620c80..7c80d8a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -965,6 +965,35 @@
}
}
+ /**
+ * Starts a session to verify lock screen credentials provided by a remote device.
+ */
+ public void startRemoteLockscreenValidation() {
+ checkVerifyRemoteLockscreenPermission();
+ // TODO(b/254335492): Create session in memory
+ return;
+ }
+
+ /**
+ * Verifies encrypted credentials guess from a remote device.
+ */
+ public void validateRemoteLockscreen(@NonNull byte[] encryptedCredential) {
+ checkVerifyRemoteLockscreenPermission();
+ // TODO(b/254335492): Decrypt and verify credentials
+ return;
+ }
+
+ private void checkVerifyRemoteLockscreenPermission() {
+ // TODO(b/254335492): Check new system permission
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.RECOVER_KEYSTORE,
+ "Caller " + Binder.getCallingUid()
+ + " doesn't have verifyRemoteLockscreen permission.");
+ int userId = UserHandle.getCallingUserId();
+ int uid = Binder.getCallingUid();
+ mCleanupManager.registerRecoveryAgent(userId, uid);
+ }
+
private void checkRecoverKeyStorePermission() {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.RECOVER_KEYSTORE,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RemoteLockscreenValidationSessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RemoteLockscreenValidationSessionStorage.java
new file mode 100644
index 0000000..7698c28
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RemoteLockscreenValidationSessionStorage.java
@@ -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.server.locksettings.recoverablekeystore.storage;
+
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.security.KeyPair;
+
+/**
+ * Memory based storage for keyPair used to send encrypted credentials from a remote device.
+ *
+ * @hide
+ */
+public class RemoteLockscreenValidationSessionStorage {
+
+ private final SparseArray<LockScreenVerificationSession> mSessionsByUserId =
+ new SparseArray<>();
+
+ /**
+ * Returns session for given user or null.
+ *
+ * @param userId The user id
+ * @return The session info.
+ *
+ * @hide
+ */
+ @Nullable
+ public LockScreenVerificationSession get(int userId) {
+ return mSessionsByUserId.get(userId);
+ }
+
+ /**
+ * Creates a new session to verify lockscreen credentials guess.
+ *
+ * Session will be automatically removed after 10 minutes of inactivity.
+ * @param userId The user id
+ *
+ * @hide
+ */
+ public LockScreenVerificationSession startSession(int userId) {
+ if (mSessionsByUserId.get(userId) != null) {
+ mSessionsByUserId.remove(userId);
+ }
+ LockScreenVerificationSession newSession = null;
+ // TODO(b/254335492): Schedule a task to remove session.
+ mSessionsByUserId.put(userId, newSession);
+ return newSession;
+ }
+
+ /**
+ * Deletes session for a user.
+ */
+ public void remove(int userId) {
+ mSessionsByUserId.remove(userId);
+ }
+
+ /**
+ * Holder for keypair used by remote lock screen validation.
+ *
+ * @hide
+ */
+ public static class LockScreenVerificationSession {
+ private final KeyPair mKeyPair;
+ private final long mSessionStartTimeMillis;
+
+ /**
+ * @hide
+ */
+ public LockScreenVerificationSession(KeyPair keyPair, long sessionStartTimeMillis) {
+ mKeyPair = keyPair;
+ mSessionStartTimeMillis = sessionStartTimeMillis;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 3ad0e44..a9b9b54 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -69,6 +69,7 @@
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -247,6 +248,24 @@
// Binder call
@Override
+ public void showMediaOutputSwitcher(String packageName) {
+ if (!validatePackageName(Binder.getCallingUid(), packageName)) {
+ throw new SecurityException("packageName must match the calling identity");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ StatusBarManagerInternal statusBar =
+ LocalServices.getService(StatusBarManagerInternal.class);
+ statusBar.showMediaOutputSwitcher(packageName);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
public MediaRouterClientState getState(IMediaRouterClient client) {
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 3df46a2..a7e0af3 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -774,6 +774,21 @@
if (!deleteAllUsers) {
returnCode = deletePackageX(internalPackageName, versionCode,
userId, deleteFlags, false /*removedBySystem*/);
+
+ // Get a list of child user profiles and delete if package is
+ // present in clone profile.
+ int[] childUserIds = mUserManagerInternal.getProfileIds(userId, true);
+ for (int childId : childUserIds) {
+ if (childId != userId) {
+ UserInfo userInfo = mUserManagerInternal.getUserInfo(childId);
+ if (userInfo != null && userInfo.isCloneProfile()) {
+ returnCode = deletePackageX(internalPackageName, versionCode,
+ childId, deleteFlags, false /*removedBySystem*/);
+ break;
+ }
+ }
+ }
+
} else {
int[] blockUninstallUserIds = getBlockUninstallForUsers(innerSnapshot,
internalPackageName, users);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index ad8e35d..b919330 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1152,8 +1152,8 @@
throw new IOException("Root has not present");
}
return ApkChecksums.verityHashForFile(new File(filename), hashInfo.rawRootHash);
- } catch (IOException ignore) {
- Slog.e(TAG, "ERROR: could not load root hash from incremental install");
+ } catch (IOException e) {
+ Slog.i(TAG, "Could not obtain verity root hash", e);
}
return null;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3340e12..762d1f6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -262,7 +262,7 @@
// We need to keep process uid within Integer.MAX_VALUE.
@VisibleForTesting
- static final int MAX_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
+ static final int MAX_USER_ID = UserHandle.MAX_SECONDARY_USER_ID;
// Max size of the queue of recently removed users
@VisibleForTesting
@@ -1779,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");
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..b8c57b8 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)
+ .setMediaSharedWithParent(true)
+ .setCredentialShareableWithParent(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)
+ .setCredentialShareableWithParent(true));
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 60751e6..5a0c344 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -664,7 +664,7 @@
dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj);
break;
case MSG_DISPATCH_SHOW_RECENTS:
- showRecents();
+ showRecentApps(false);
break;
case MSG_DISPATCH_SHOW_GLOBAL_ACTIONS:
showGlobalActionsInternal();
@@ -2927,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:
@@ -3110,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;
@@ -3662,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/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 94fb840..ca5fa5f 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -67,6 +67,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -91,6 +92,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
/**
* This interface supplies all UI-specific behavior of the window manager. An
@@ -357,6 +359,11 @@
* windows even if the rotation hasn't changed.
*/
void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout);
+
+ /**
+ * Invoked when a screenshot is taken of the given display to notify registered listeners.
+ */
+ List<ComponentName> notifyScreenshotListeners(int displayId);
}
/**
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/soundtrigger_middleware/OWNERS b/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
index e5d0370..01b2cb9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
+++ b/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
@@ -1,2 +1,2 @@
-ytai@google.com
+atneya@google.com
elaurent@google.com
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/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 73b2238..29b37ce 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2184,10 +2184,10 @@
return null;
}
- final long identity = Binder.clearCallingIdentity();
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "acquireTvInputHardware");
+ final long identity = Binder.clearCallingIdentity();
try {
return mTvInputHardwareManager.acquireHardware(
deviceId, callback, info, callingUid, resolvedUserId,
@@ -2205,10 +2205,10 @@
return;
}
- final long identity = Binder.clearCallingIdentity();
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "releaseTvInputHardware");
+ final long identity = Binder.clearCallingIdentity();
try {
mTvInputHardwareManager.releaseHardware(
deviceId, hardware, callingUid, resolvedUserId);
@@ -2350,10 +2350,10 @@
throws RemoteException {
ensureCaptureTvInputPermission();
- final long identity = Binder.clearCallingIdentity();
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "getAvailableTvStreamConfigList");
+ final long identity = Binder.clearCallingIdentity();
try {
return mTvInputHardwareManager.getAvailableTvStreamConfigList(
inputId, callingUid, resolvedUserId);
@@ -2368,10 +2368,10 @@
throws RemoteException {
ensureCaptureTvInputPermission();
- final long identity = Binder.clearCallingIdentity();
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "captureFrame");
+ final long identity = Binder.clearCallingIdentity();
try {
String hardwareInputId = null;
synchronized (mLock) {
@@ -2400,10 +2400,10 @@
@Override
public boolean isSingleSessionActive(int userId) throws RemoteException {
ensureCaptureTvInputPermission();
- final long identity = Binder.clearCallingIdentity();
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "isSingleSessionActive");
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
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/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 90ac1aa..fd3f32d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -249,6 +249,7 @@
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
import android.app.ICompatCameraControlCallback;
+import android.app.IScreenCaptureObserver;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.ResultInfo;
@@ -301,6 +302,7 @@
import android.os.LocaleList;
import android.os.PersistableBundle;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -886,6 +888,8 @@
private AppSaturationInfo mLastAppSaturationInfo;
+ private RemoteCallbackList<IScreenCaptureObserver> mCaptureCallbacks;
+
private final ColorDisplayService.ColorTransformController mColorTransformController =
(matrix, translation) -> mWmService.mH.post(() -> {
synchronized (mWmService.mGlobalLock) {
@@ -7020,6 +7024,41 @@
return mLocusId;
}
+ public void reportScreenCaptured() {
+ if (mCaptureCallbacks != null) {
+ final int n = mCaptureCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IScreenCaptureObserver obs = mCaptureCallbacks.getBroadcastItem(i);
+ try {
+ obs.onScreenCaptured();
+ } catch (RemoteException e) {
+ }
+ }
+ mCaptureCallbacks.finishBroadcast();
+ }
+ }
+
+ public void registerCaptureObserver(IScreenCaptureObserver observer) {
+ synchronized (mWmService.mGlobalLock) {
+ if (mCaptureCallbacks == null) {
+ mCaptureCallbacks = new RemoteCallbackList<IScreenCaptureObserver>();
+ }
+ mCaptureCallbacks.register(observer);
+ }
+ }
+
+ public void unregisterCaptureObserver(IScreenCaptureObserver observer) {
+ synchronized (mWmService.mGlobalLock) {
+ if (mCaptureCallbacks != null) {
+ mCaptureCallbacks.unregister(observer);
+ }
+ }
+ }
+
+ boolean isRegisteredForScreenCaptureCallback() {
+ return mCaptureCallbacks != null && mCaptureCallbacks.getRegisteredCallbackCount() > 0;
+ }
+
void setVoiceSessionLocked(IVoiceInteractionSession session) {
voiceSession = session;
pendingVoiceInteractionStart = false;
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 64af9dd..47e78f0 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -72,7 +72,7 @@
}
@GuardedBy("ActivityTaskManagerService.mGlobalLock")
- static boolean shouldBlockActivityStart(int uid) {
+ static boolean shouldRestrictActivitySwitch(int uid) {
return flagEnabledForUid(sAsmRestrictionsEnabled, uid);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d88f719..d6d3dc7 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1972,7 +1972,7 @@
);
boolean shouldBlockActivityStart =
- ActivitySecurityModelFeatureFlags.shouldBlockActivityStart(mCallingUid);
+ ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(mCallingUid);
if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) {
UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
@@ -2080,6 +2080,7 @@
reusedTask != null ? reusedTask.getTopNonFinishingActivity() : null, intentGrants);
if (mAddingToTask) {
+ clearTopIfNeeded(targetTask, mCallingUid, mStartActivity.getUid(), mLaunchFlags);
return START_SUCCESS;
}
@@ -2112,6 +2113,55 @@
}
/**
+ * If the top activity uid does not match the launched activity, and the launch was not
+ * requested from the top uid, we want to clear out all non matching activities to prevent the
+ * top activity being sandwiched.
+ */
+ private void clearTopIfNeeded(@NonNull Task targetTask, int callingUid, int startingUid,
+ int launchFlags) {
+ if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK) {
+ // Launch is from the same task, so must be a top or privileged UID
+ return;
+ }
+
+ ActivityRecord targetTaskTop = targetTask.getTopNonFinishingActivity();
+ if (targetTaskTop != null && targetTaskTop.getUid() != startingUid) {
+ boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(callingUid);
+ int[] finishCount = new int[0];
+ if (shouldBlockActivityStart) {
+ ActivityRecord activity = targetTask.getActivity(
+ ar -> !ar.finishing && ar.isUid(startingUid));
+
+ if (activity == null) {
+ // mStartActivity is not in task, so clear everything
+ activity = mStartActivity;
+ }
+
+ finishCount = new int[1];
+ if (activity != null) {
+ targetTask.performClearTop(activity, launchFlags, finishCount);
+ }
+
+ if (finishCount[0] > 0) {
+ Slog.w(TAG, "Clearing top n: " + finishCount[0] + " activities from task t: "
+ + targetTask + " not matching top uid: " + callingUid);
+ }
+ }
+
+ if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)
+ && (!shouldBlockActivityStart || finishCount[0] > 0)) {
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ (shouldBlockActivityStart
+ ? "Top activities cleared by "
+ : "Top activities would be cleared by ")
+ + ActivitySecurityModelFeatureFlags.DOC_LINK,
+ Toast.LENGTH_SHORT).show());
+ }
+ }
+ }
+
+ /**
* Check if the activity being launched is the same as the one currently at the top and it
* should only be launched once.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a927ed3..2f82167 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.BIND_VOICE_INTERACTION;
import static android.Manifest.permission.CHANGE_CONFIGURATION;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
@@ -145,6 +146,7 @@
import android.app.IApplicationThread;
import android.app.IAssistDataReceiver;
import android.app.INotificationManager;
+import android.app.IScreenCaptureObserver;
import android.app.ITaskStackListener;
import android.app.Notification;
import android.app.NotificationManager;
@@ -5437,6 +5439,32 @@
}
}
+ @Override
+ public void registerScreenCaptureObserver(IBinder activityToken,
+ IScreenCaptureObserver observer) {
+ mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,
+ "registerScreenCaptureObserver");
+ synchronized (mGlobalLock) {
+ ActivityRecord activityRecord = ActivityRecord.forTokenLocked(activityToken);
+ if (activityRecord != null) {
+ activityRecord.registerCaptureObserver(observer);
+ }
+ }
+ }
+
+ @Override
+ public void unregisterScreenCaptureObserver(IBinder activityToken,
+ IScreenCaptureObserver observer) {
+ mAmInternal.enforceCallingPermission(DETECT_SCREEN_CAPTURE,
+ "unregisterScreenCaptureObserver");
+ synchronized (mGlobalLock) {
+ ActivityRecord activityRecord = ActivityRecord.forTokenLocked(activityToken);
+ if (activityRecord != null) {
+ activityRecord.unregisterCaptureObserver(observer);
+ }
+ }
+ }
+
/**
* Returns {@code true} if the process represented by the pid passed as argument is
* instrumented and the instrumentation source was granted with the permission also
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 8149e1c..0f1f51f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -137,6 +137,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
+import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -147,6 +148,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
+import com.android.server.UiThread;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.HostingRecord;
import com.android.server.am.UserState;
@@ -1628,16 +1630,16 @@
// Prevent recursion.
return;
}
+ boolean passesAsmChecks = true;
// We may have already checked that the callingUid has additional clearTask privileges, and
// cleared the calling identify. If so, we infer we do not need further restrictions here.
// TODO(b/263368846) Move to live with the rest of the ASM logic.
if (callingUid != SYSTEM_UID) {
- boolean passesAsmChecks = doesTopActivityMatchingUidExistForAsm(task, callingUid,
+ passesAsmChecks = doesTopActivityMatchingUidExistForAsm(task, callingUid,
null);
if (!passesAsmChecks) {
ActivityRecord topActivity = task.getActivity(ar ->
!ar.isState(FINISHING) && !ar.isAlwaysOnTop());
- Slog.i(TAG, "Finishing task from background. t: " + task);
FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
/* caller_uid */
callingUid,
@@ -1676,6 +1678,28 @@
if (task.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
+ if (!passesAsmChecks) {
+ boolean shouldRestrictActivitySwitch =
+ ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(callingUid);
+
+ if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ (shouldRestrictActivitySwitch
+ ? "Returning home due to "
+ : "Would return home due to ")
+ + ActivitySecurityModelFeatureFlags.DOC_LINK,
+ Toast.LENGTH_SHORT).show());
+ }
+
+ // If the activity switch should be restricted, return home rather than the
+ // previously top task, to prevent users from being confused which app they're
+ // viewing
+ if (shouldRestrictActivitySwitch) {
+ Slog.w(TAG, "Return to home as source uid: " + callingUid
+ + "is not on top of task t: " + task);
+ task.getTaskDisplayArea().moveHomeActivityToTop("taskRemoved");
+ }
+ }
} finally {
task.mInRemoveTask = false;
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index d65c2f9..2b8b59c 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -901,9 +901,18 @@
* TODO(b/213312721): Remove this predicate and its callers once ShellTransition is enabled.
*/
static boolean isTaskViewTask(WindowContainer wc) {
- // We use Task#mRemoveWithTaskOrganizer to identify an embedded Task, but this is a hack and
+ // Use Task#mRemoveWithTaskOrganizer to identify an embedded Task, but this is a hack and
// it is not guaranteed to work this logic in the future version.
- return wc instanceof Task && ((Task) wc).mRemoveWithTaskOrganizer;
+ boolean isTaskViewTask = wc instanceof Task && ((Task) wc).mRemoveWithTaskOrganizer;
+ if (isTaskViewTask) {
+ return true;
+ }
+
+ WindowContainer parent = wc.getParent();
+ boolean isParentATaskViewTask = parent != null
+ && parent instanceof Task
+ && ((Task) parent).mRemoveWithTaskOrganizer;
+ return isParentATaskViewTask;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0bd59a8..1794e2a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5264,6 +5264,11 @@
return b;
}
+ // WARNING: it says `mSurfaceControl` below, but this CHANGES meaning after construction!
+ // DisplayAreas are added in `configureSurface()` *before* `mSurfaceControl` gets replaced
+ // with a wrapper or magnification surface so they end up in the right place; however,
+ // anything added or reparented to "the display" *afterwards* needs to be reparented to
+ // `getWindowinglayer()` (unless it's an overlay DisplayArea).
return b.setName(child.getName())
.setParent(mSurfaceControl);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 67e188f..d2f62da 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -724,6 +724,7 @@
* <li>Activity is portrait-only.
* <li>Fullscreen window in landscape device orientation.
* <li>Horizontal Reachability is enabled.
+ * <li>Activity fills parent vertically.
* </ul>
*/
private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
@@ -731,10 +732,14 @@
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
&& (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
- && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT);
+ && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT)
+ // Check whether the activity fills the parent vertically.
+ && parentConfiguration.windowConfiguration.getBounds().height()
+ == mActivityRecord.getBounds().height();
}
- private boolean isHorizontalReachabilityEnabled() {
+ @VisibleForTesting
+ boolean isHorizontalReachabilityEnabled() {
return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
}
@@ -746,6 +751,7 @@
* <li>Activity is landscape-only.
* <li>Fullscreen window in portrait device orientation.
* <li>Vertical Reachability is enabled.
+ * <li>Activity fills parent horizontally.
* </ul>
*/
private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
@@ -753,10 +759,14 @@
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
&& (parentConfiguration.orientation == ORIENTATION_PORTRAIT
- && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE);
+ && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE)
+ // Check whether the activity fills the parent horizontally.
+ && parentConfiguration.windowConfiguration.getBounds().width()
+ == mActivityRecord.getBounds().width();
}
- private boolean isVerticalReachabilityEnabled() {
+ @VisibleForTesting
+ boolean isVerticalReachabilityEnabled() {
return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7d3367f..7b56b0c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1650,6 +1650,12 @@
// DisplayContent is the "root", so we reinterpret it's wc as the window layer
// making the parent surface the displaycontent's surface.
return wc.getSurfaceControl();
+ } else if (wc.getParent().asDisplayContent() != null) {
+ // DisplayContent is kinda split into 2 pieces, the "real root" and the
+ // "windowing layer". So if the parent of the window is DC, then it really belongs on
+ // the windowing layer (unless it's an overlay display area, but those can't be in
+ // transitions anyways).
+ return wc.getParent().asDisplayContent().getWindowingLayer();
}
return wc.getParent().getSurfaceControl();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bded45e..cb1b3e4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -26,6 +26,7 @@
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
@@ -168,6 +169,7 @@
import android.app.IAssistDataReceiver;
import android.app.WindowConfiguration;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -9447,4 +9449,55 @@
public void markSurfaceSyncGroupReady(IBinder syncGroupToken) {
mSurfaceSyncGroupController.markSyncGroupReady(syncGroupToken);
}
+
+ private ArraySet<ActivityRecord> getVisibleActivityRecords(int displayId) {
+ ArraySet<ActivityRecord> result = new ArraySet<>();
+ synchronized (mGlobalLock) {
+ ArraySet<ComponentName> addedActivities = new ArraySet<>();
+ DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent != null) {
+ displayContent.forAllWindows(
+ (w) -> {
+ if (w.isVisible()
+ && w.isDisplayed()
+ && w.mActivityRecord != null
+ && !addedActivities.contains(
+ w.mActivityRecord.mActivityComponent)
+ && w.mActivityRecord.isVisible()
+ && w.isVisibleNow()) {
+ addedActivities.add(w.mActivityRecord.mActivityComponent);
+ result.add(w.mActivityRecord);
+ }
+ },
+ true /* traverseTopToBottom */);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Must be called when a screenshot is taken via hardware chord.
+ *
+ * Notifies all registered visible activities that have registered for screencapture callback,
+ * Returns a list of visible apps component names.
+ */
+ @Override
+ public List<ComponentName> notifyScreenshotListeners(int displayId) {
+ // make sure caller is SysUI.
+ if (!checkCallingPermission(STATUS_BAR_SERVICE,
+ "notifyScreenshotListeners()")) {
+ throw new SecurityException("Requires STATUS_BAR_SERVICE permission");
+ }
+ synchronized (mGlobalLock) {
+ ArraySet<ComponentName> notifiedApps = new ArraySet<>();
+ ArraySet<ActivityRecord> visibleApps = getVisibleActivityRecords(displayId);
+ for (ActivityRecord ar : visibleApps) {
+ if (ar.isRegisteredForScreenCaptureCallback()) {
+ ar.reportScreenCaptured();
+ notifiedApps.add(ar.mActivityComponent);
+ }
+ }
+ return List.copyOf(notifiedApps);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 2980c76..7dfc132 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1015,7 +1015,7 @@
/**
* Returns display UI context list which there is any app window shows or starting activities
- * int this process.
+ * in this process.
*/
public void getDisplayContextsWithErrorDialogs(List<Context> displayContexts) {
if (displayContexts == null) {
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index d3d69ae..4af685e 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -146,6 +146,7 @@
// vmas vector, instead it iterates on it until the end.
class VmaBatchCreator {
const std::vector<Vma>* sourceVmas;
+ const int totalVmasInSource;
// This is the destination array where batched VMAs will be stored
// it gets encapsulated into a VmaBatch which is the object
// meant to be used by client code.
@@ -156,8 +157,13 @@
uint64_t currentOffset_;
public:
- VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec)
- : sourceVmas(vmasToBatch), destVmas(destVmasVec), currentIndex_(0), currentOffset_(0) {}
+ VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec,
+ int vmasInSource)
+ : sourceVmas(vmasToBatch),
+ totalVmasInSource(vmasInSource),
+ destVmas(destVmasVec),
+ currentIndex_(0),
+ currentOffset_(0) {}
int currentIndex() { return currentIndex_; }
uint64_t currentOffset() { return currentOffset_; }
@@ -177,7 +183,7 @@
// Add VMAs to the batch up until we consumed all the VMAs or
// reached any imposed limit of VMAs per batch.
- while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < vmas.size()) {
+ while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < totalVmasInSource) {
uint64_t vmaStart = vmas[currentIndex_].start + currentOffset_;
uint64_t vmaSize = vmas[currentIndex_].end - vmaStart;
uint64_t bytesAvailableInBatch = MAX_BYTES_PER_BATCH - totalBytesInBatch;
@@ -272,8 +278,9 @@
//
// If any VMA fails compaction due to -EINVAL it will be skipped and continue.
// However, if it fails for any other reason, it will bail out and forward the error
-static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
- if (vmas.empty()) {
+static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType,
+ int totalVmas) {
+ if (totalVmas == 0) {
return 0;
}
@@ -286,7 +293,7 @@
struct iovec destVmas[MAX_VMAS_PER_BATCH];
VmaBatch batch;
- VmaBatchCreator batcher(&vmas, destVmas);
+ VmaBatchCreator batcher(&vmas, destVmas, totalVmas);
int64_t totalBytesProcessed = 0;
while (batcher.createNextBatch(batch)) {
@@ -346,34 +353,53 @@
// Returns the total number of bytes compacted on success. On error
// returns process_madvise errno code or if compaction was cancelled
// it returns ERROR_COMPACTION_CANCELLED.
+//
+// Not thread safe. We reuse vectors so we assume this is called only
+// on one thread at most.
static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) {
cancelRunningCompaction.store(false);
-
+ static std::string mapsBuffer;
ATRACE_BEGIN("CollectVmas");
ProcMemInfo meminfo(pid);
- std::vector<Vma> pageoutVmas, coldVmas;
- auto vmaCollectorCb = [&coldVmas,&pageoutVmas,&vmaToAdviseFunc](const Vma& vma) {
+ static std::vector<Vma> pageoutVmas(2000), coldVmas(2000);
+ int coldVmaIndex = 0;
+ int pageoutVmaIndex = 0;
+ auto vmaCollectorCb = [&vmaToAdviseFunc, &pageoutVmaIndex, &coldVmaIndex](const Vma& vma) {
int advice = vmaToAdviseFunc(vma);
switch (advice) {
case MADV_COLD:
- coldVmas.push_back(vma);
+ if (coldVmaIndex < coldVmas.size()) {
+ coldVmas[coldVmaIndex] = vma;
+ } else {
+ coldVmas.push_back(vma);
+ }
+ ++coldVmaIndex;
break;
case MADV_PAGEOUT:
- pageoutVmas.push_back(vma);
+ if (pageoutVmaIndex < pageoutVmas.size()) {
+ pageoutVmas[pageoutVmaIndex] = vma;
+ } else {
+ pageoutVmas.push_back(vma);
+ }
+ ++pageoutVmaIndex;
break;
}
};
- meminfo.ForEachVmaFromMaps(vmaCollectorCb);
+ meminfo.ForEachVmaFromMaps(vmaCollectorCb, mapsBuffer);
ATRACE_END();
+#ifdef DEBUG_COMPACTION
+ ALOGE("Total VMAs sent for compaction anon=%d file=%d", pageoutVmaIndex,
+ coldVmaIndex);
+#endif
- int64_t pageoutBytes = compactMemory(pageoutVmas, pid, MADV_PAGEOUT);
+ int64_t pageoutBytes = compactMemory(pageoutVmas, pid, MADV_PAGEOUT, pageoutVmaIndex);
if (pageoutBytes < 0) {
// Error, just forward it.
cancelRunningCompaction.store(false);
return pageoutBytes;
}
- int64_t coldBytes = compactMemory(coldVmas, pid, MADV_COLD);
+ int64_t coldBytes = compactMemory(coldVmas, pid, MADV_COLD, coldVmaIndex);
if (coldBytes < 0) {
// Error, just forward it.
cancelRunningCompaction.store(false);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
deleted file mode 100644
index 834f65f..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.devicepolicy;
-
-import android.accounts.Account;
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.admin.DevicePolicyDrawableResource;
-import android.app.admin.DevicePolicySafetyChecker;
-import android.app.admin.DevicePolicyStringResource;
-import android.app.admin.FullyManagedDeviceProvisioningParams;
-import android.app.admin.IDevicePolicyManager;
-import android.app.admin.ManagedProfileProvisioningParams;
-import android.app.admin.ParcelableResource;
-import android.content.ComponentName;
-import android.os.UserHandle;
-import android.util.Slog;
-
-import com.android.server.SystemService;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Defines the required interface for IDevicePolicyManager implemenation.
- *
- * <p>The interface consists of public parts determined by {@link IDevicePolicyManager} and also
- * several package private methods required by internal infrastructure.
- *
- * <p>Whenever adding an AIDL method to {@link IDevicePolicyManager}, an empty override method
- * should be added here to avoid build breakage in downstream branches.
- */
-abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
-
- private static final String TAG = BaseIDevicePolicyManager.class.getSimpleName();
-
- /**
- * To be called by {@link DevicePolicyManagerService#Lifecycle} during the various boot phases.
- *
- * @see {@link SystemService#onBootPhase}.
- */
- abstract void systemReady(int phase);
- /**
- * To be called by {@link DevicePolicyManagerService#Lifecycle} when a new user starts.
- *
- * @see {@link SystemService#onUserStarting}
- */
- abstract void handleStartUser(int userId);
- /**
- * To be called by {@link DevicePolicyManagerService#Lifecycle} when a user is being unlocked.
- *
- * @see {@link SystemService#onUserUnlocking}
- */
- abstract void handleUnlockUser(int userId);
- /**
- * To be called by {@link DevicePolicyManagerService#Lifecycle} after a user is being unlocked.
- *
- * @see {@link SystemService#onUserUnlocked}
- */
- abstract void handleOnUserUnlocked(int userId);
- /**
- * To be called by {@link DevicePolicyManagerService#Lifecycle} when a user is being stopped.
- *
- * @see {@link SystemService#onUserStopping}
- */
- abstract void handleStopUser(int userId);
-
- /**
- * Sets the {@link DevicePolicySafetyChecker}.
- *
- * <p>Currently, it's called only by {@code SystemServer} on
- * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}
- */
- public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
- Slog.w(TAG, "setDevicePolicySafetyChecker() not implemented by " + getClass());
- }
-
- public void clearSystemUpdatePolicyFreezePeriodRecord() {
- }
-
- public boolean setKeyGrantForApp(ComponentName admin, String callerPackage, String alias,
- String packageName, boolean hasGrant) {
- return false;
- }
-
- public void setLocationEnabled(ComponentName who, boolean locationEnabled) {}
-
- public boolean isOrganizationOwnedDeviceWithManagedProfile() {
- return false;
- }
-
- public int getPersonalAppsSuspendedReasons(ComponentName admin) {
- return 0;
- }
-
- public void setPersonalAppsSuspended(ComponentName admin, boolean suspended) {
- }
-
- public void setManagedProfileMaximumTimeOff(ComponentName admin, long timeoutMs) {
- }
-
- public long getManagedProfileMaximumTimeOff(ComponentName admin) {
- return 0;
- }
-
- @Override
- public void acknowledgeDeviceCompliant() {}
-
- @Override
- public boolean isComplianceAcknowledgementRequired() {
- return false;
- }
-
- public boolean canProfileOwnerResetPasswordWhenLocked(int userId) {
- return false;
- }
-
- public String getEnrollmentSpecificId(String callerPackage) {
- return "";
- }
-
- public void setOrganizationIdForUser(
- @NonNull String callerPackage, @NonNull String enterpriseId, int userId) {}
-
- public UserHandle createAndProvisionManagedProfile(
- @NonNull ManagedProfileProvisioningParams provisioningParams, String callerPackage) {
- return null;
- }
-
- public void finalizeWorkProfileProvisioning(
- UserHandle managedProfileUser, Account migratedAccount) {
-
- }
-
- public void provisionFullyManagedDevice(
- FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) {
- }
-
- @Override
- public void setDeviceOwnerType(@NonNull ComponentName admin, int deviceOwnerType) {
- }
-
- @Override
- public int getDeviceOwnerType(@NonNull ComponentName admin) {
- return 0;
- }
-
- public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) {}
-
- public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
- return false;
- }
-
- @Override
- public boolean setKeyGrantToWifiAuth(String callerPackage, String alias, boolean hasGrant) {
- return false;
- }
-
- @Override
- public boolean isKeyPairGrantedToWifiAuth(String callerPackage, String alias) {
- return false;
- }
-
- @Override
- public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables){}
-
- @Override
- public void resetDrawables(@NonNull List<String> drawableIds){}
-
- @Override
- public ParcelableResource getDrawable(
- String drawableId, String drawableStyle, String drawableSource) {
- return null;
- }
-
- @Override
- public void setStrings(@NonNull List<DevicePolicyStringResource> strings){}
-
- @Override
- public void resetStrings(@NonNull List<String> stringIds){}
-
- @Override
- public ParcelableResource getString(String stringId) {
- return null;
- }
-
- @Override
- public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification() {
- return false;
- }
-
- @Override
- public List<UserHandle> getPolicyManagedProfiles(UserHandle userHandle) {
- return Collections.emptyList();
- }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0963e3b..3470b04 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -221,6 +221,7 @@
import android.app.admin.DeviceStateCache;
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.FullyManagedDeviceProvisioningParams;
+import android.app.admin.IDevicePolicyManager;
import android.app.admin.ManagedProfileProvisioningParams;
import android.app.admin.ManagedSubscriptionsPolicy;
import android.app.admin.NetworkEvent;
@@ -442,7 +443,7 @@
/**
* Implementation of the device policy APIs.
*/
-public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
+public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
protected static final String LOG_TAG = "DevicePolicyManager";
@@ -739,6 +740,10 @@
private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
+ private static final String ENABLE_WORK_PROFILE_TELEPHONY_FLAG =
+ "enable_work_profile_telephony";
+ private static final boolean DEFAULT_WORK_PROFILE_TELEPHONY_FLAG = false;
+
// TODO(b/261999445) remove the flag after rollout.
private static final String HEADLESS_FLAG = "headless";
private static final boolean DEFAULT_HEADLESS_FLAG = true;
@@ -920,23 +925,24 @@
private final ArrayList<Object> mPendingUserCreatedCallbackTokens = new ArrayList<>();
public static final class Lifecycle extends SystemService {
- private BaseIDevicePolicyManager mService;
+ private DevicePolicyManagerService mService;
public Lifecycle(Context context) {
super(context);
String dpmsClassName = context.getResources()
.getString(R.string.config_deviceSpecificDevicePolicyManagerService);
if (TextUtils.isEmpty(dpmsClassName)) {
- dpmsClassName = DevicePolicyManagerService.class.getName();
- }
- try {
- Class<?> serviceClass = Class.forName(dpmsClassName);
- Constructor<?> constructor = serviceClass.getConstructor(Context.class);
- mService = (BaseIDevicePolicyManager) constructor.newInstance(context);
- } catch (Exception e) {
- throw new IllegalStateException(
- "Failed to instantiate DevicePolicyManagerService with class name: "
- + dpmsClassName, e);
+ mService = new DevicePolicyManagerService(context);
+ } else {
+ try {
+ Class<?> serviceClass = Class.forName(dpmsClassName);
+ Constructor<?> constructor = serviceClass.getConstructor(Context.class);
+ mService = (DevicePolicyManagerService) constructor.newInstance(context);
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "Failed to instantiate DevicePolicyManagerService with class name: "
+ + dpmsClassName, e);
+ }
}
}
@@ -1348,7 +1354,6 @@
}
}
- @Override
public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
CallerIdentity callerIdentity = getCallerIdentity();
Preconditions.checkCallAuthorization(mIsAutomotive || isAdb(callerIdentity), "can only set "
@@ -3090,7 +3095,6 @@
}
@VisibleForTesting
- @Override
void systemReady(int phase) {
if (!mHasFeature) {
return;
@@ -3100,7 +3104,9 @@
onLockSettingsReady();
loadAdminDataAsync();
mOwners.systemReady();
- applyManagedSubscriptionsPolicyIfRequired();
+ if (isWorkProfileTelephonyFlagEnabled()) {
+ applyManagedSubscriptionsPolicyIfRequired();
+ }
break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
synchronized (getLockObject()) {
@@ -3289,7 +3295,6 @@
}
}
- @Override
void handleStartUser(int userId) {
synchronized (getLockObject()) {
pushScreenCapturePolicy(userId);
@@ -3337,7 +3342,6 @@
targetUserId, protectedPackages));
}
- @Override
void handleUnlockUser(int userId) {
startOwnerService(userId, "unlock-user");
if (isCoexistenceFlagEnabled()) {
@@ -3345,12 +3349,10 @@
}
}
- @Override
void handleOnUserUnlocked(int userId) {
showNewUserDisclaimerIfNecessary(userId);
}
- @Override
void handleStopUser(int userId) {
updateNetworkPreferenceForUser(userId, List.of(PreferentialNetworkServiceConfig.DEFAULT));
mDeviceAdminServiceController.stopServicesForUser(userId, /* actionForLog= */ "stop-user");
@@ -7018,8 +7020,9 @@
}
mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
- clearManagedSubscriptionsPolicy();
-
+ if (isWorkProfileTelephonyFlagEnabled()) {
+ clearManagedSubscriptionsPolicy();
+ }
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
}
@@ -10132,6 +10135,9 @@
synchronized (mSubscriptionsChangedListenerLock) {
pw.println("Subscription changed listener : " + mSubscriptionsChangedListener);
}
+ pw.println(
+ "Flag enable_work_profile_telephony : " + isWorkProfileTelephonyFlagEnabled());
+
mHandler.post(() -> handleDump(pw));
dumpResources(pw);
}
@@ -20104,6 +20110,13 @@
DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
}
+ private static boolean isWorkProfileTelephonyFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ ENABLE_WORK_PROFILE_TELEPHONY_FLAG,
+ DEFAULT_WORK_PROFILE_TELEPHONY_FLAG);
+ }
+
@Override
public void setMtePolicy(int flags) {
final Set<Integer> allowedModes =
@@ -20184,10 +20197,12 @@
@Override
public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
- synchronized (getLockObject()) {
- ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
- if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
- return admin.mManagedSubscriptionsPolicy;
+ if (isWorkProfileTelephonyFlagEnabled()) {
+ synchronized (getLockObject()) {
+ ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
+ if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
+ return admin.mManagedSubscriptionsPolicy;
+ }
}
}
return new ManagedSubscriptionsPolicy(
@@ -20196,9 +20211,13 @@
@Override
public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) {
+ if (!isWorkProfileTelephonyFlagEnabled()) {
+ throw new UnsupportedOperationException("This api is not enabled");
+ }
CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller),
- "This policy can only be set by a profile owner on an organization-owned device.");
+ "This policy can only be set by a profile owner on an organization-owned "
+ + "device.");
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
@@ -20316,4 +20335,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/services/incremental/TEST_MAPPING b/services/incremental/TEST_MAPPING
index 9fe090a..3976a70 100644
--- a/services/incremental/TEST_MAPPING
+++ b/services/incremental/TEST_MAPPING
@@ -36,5 +36,10 @@
}
]
}
+ ],
+ "kernel-presubmit": [
+ {
+ "name": "CtsIncrementalInstallHostTestCases"
+ }
]
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b1cc306..a15c6d2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1021,9 +1021,7 @@
Runnable runnable = new Runnable() {
@Override
public void run() {
- synchronized (this) {
- ShutdownThread.rebootOrShutdown(null, reboot, reason);
- }
+ ShutdownThread.rebootOrShutdown(null, reboot, reason);
}
};
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index f7efcd1..e711cab 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -32,7 +32,6 @@
"services.people",
"services.usage",
"guava",
- "guava-android-testlib",
"androidx.test.core",
"androidx.test.ext.truth",
"androidx.test.runner",
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 33ac735..ea0481e 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -43,6 +43,9 @@
<!-- needed by TrustManagerServiceTest to access LockSettings' secure storage -->
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <!-- needed by GameManagerServiceTest because GameManager creates a UidObserver -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+
<application android:testOnly="true"
android:debuggable="true">
<uses-library android:name="android.test.runner" />
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/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index c4c50424..32243f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -36,6 +36,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -45,6 +46,7 @@
import android.Manifest;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameModeConfiguration;
import android.app.GameModeInfo;
@@ -2212,4 +2214,106 @@
assertEquals(expectedInterventionListOutput,
gameManagerService.getInterventionList(mPackageName, USER_ID_1));
}
+
+ @Test
+ public void testGamePowerMode_gamePackage() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {mPackageName};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ }
+
+ @Test
+ public void testGamePowerMode_twoGames() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages1 = {mPackageName};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
+ String someGamePkg = "some.game";
+ String[] packages2 = {someGamePkg};
+ int somePackageId = DEFAULT_PACKAGE_UID + 1;
+ when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ HashMap<Integer, Boolean> powerState = new HashMap<>();
+ doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
+ .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ assertTrue(powerState.get(Mode.GAME));
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ assertTrue(powerState.get(Mode.GAME));
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ assertFalse(powerState.get(Mode.GAME));
+ }
+
+ @Test
+ public void testGamePowerMode_twoGamesOverlap() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages1 = {mPackageName};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
+ String someGamePkg = "some.game";
+ String[] packages2 = {someGamePkg};
+ int somePackageId = DEFAULT_PACKAGE_UID + 1;
+ when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ }
+
+ @Test
+ public void testGamePowerMode_released() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {mPackageName};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ }
+
+ @Test
+ public void testGamePowerMode_noPackage() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ }
+
+ @Test
+ public void testGamePowerMode_notAGamePackage() throws Exception {
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {"someapp"};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ }
+
+ @Test
+ public void testGamePowerMode_notAGamePackageNotReleased() throws Exception {
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {"someapp"};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
+ }
}
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 f2cba40..2a790a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -80,6 +80,8 @@
@Mock
private DisplayBlanker mDisplayBlankerMock;
@Mock
+ private HighBrightnessModeMetadata mHighBrightnessModeMetadataMock;
+ @Mock
private LogicalDisplay mLogicalDisplayMock;
@Mock
private DisplayDevice mDisplayDeviceMock;
@@ -169,7 +171,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 4f8cb88..d99ed78 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -84,6 +84,8 @@
@Mock
private DisplayDevice mDisplayDeviceMock;
@Mock
+ private HighBrightnessModeMetadata mHighBrightnessModeMetadataMock;
+ @Mock
private BrightnessTracker mBrightnessTrackerMock;
@Mock
private BrightnessSetting mBrightnessSettingMock;
@@ -151,7 +153,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/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index d2ee9ff..6df54a6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -19,6 +19,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
@@ -132,6 +133,98 @@
}
@Test
+ public void testCanRunInBatterySaver_regular() {
+ final JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar")).build();
+ JobStatus job = createJobStatus(jobInfo);
+ assertFalse(job.canRunInBatterySaver());
+ job.disallowRunInBatterySaverAndDoze();
+ assertFalse(job.canRunInBatterySaver());
+ }
+
+ @Test
+ public void testCanRunInBatterySaver_expedited() {
+ final JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setExpedited(true)
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+ markExpeditedQuotaApproved(job, true);
+ assertTrue(job.canRunInBatterySaver());
+ job.disallowRunInBatterySaverAndDoze();
+ assertFalse(job.canRunInBatterySaver());
+
+ // Reset the job
+ job = createJobStatus(jobInfo);
+ markExpeditedQuotaApproved(job, true);
+ spyOn(job);
+ when(job.shouldTreatAsExpeditedJob()).thenReturn(false);
+ job.startedAsExpeditedJob = true;
+ assertTrue(job.canRunInBatterySaver());
+ job.disallowRunInBatterySaverAndDoze();
+ assertFalse(job.canRunInBatterySaver());
+ }
+
+ @Test
+ public void testCanRunInBatterySaver_userInitiated() {
+ final JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setUserInitiated(true)
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+ assertTrue(job.canRunInBatterySaver());
+ // User-initiated privilege should trump bs & doze requirement.
+ job.disallowRunInBatterySaverAndDoze();
+ assertTrue(job.canRunInBatterySaver());
+ }
+
+ @Test
+ public void testCanRunInDoze_regular() {
+ final JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar")).build();
+ JobStatus job = createJobStatus(jobInfo);
+ assertFalse(job.canRunInDoze());
+ job.disallowRunInBatterySaverAndDoze();
+ assertFalse(job.canRunInDoze());
+ }
+
+ @Test
+ public void testCanRunInDoze_expedited() {
+ final JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setExpedited(true)
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+ markExpeditedQuotaApproved(job, true);
+ assertTrue(job.canRunInDoze());
+ job.disallowRunInBatterySaverAndDoze();
+ assertFalse(job.canRunInDoze());
+
+ // Reset the job
+ job = createJobStatus(jobInfo);
+ markExpeditedQuotaApproved(job, true);
+ spyOn(job);
+ when(job.shouldTreatAsExpeditedJob()).thenReturn(false);
+ job.startedAsExpeditedJob = true;
+ assertTrue(job.canRunInDoze());
+ job.disallowRunInBatterySaverAndDoze();
+ assertFalse(job.canRunInDoze());
+ }
+
+ @Test
+ public void testCanRunInDoze_userInitiated() {
+ final JobInfo jobInfo =
+ new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setUserInitiated(true)
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+ assertTrue(job.canRunInDoze());
+ // User-initiated privilege should trump bs & doze requirement.
+ job.disallowRunInBatterySaverAndDoze();
+ assertTrue(job.canRunInDoze());
+ }
+
+ @Test
public void testMediaBackupExemption_lateConstraint() {
final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
.addTriggerContentUri(new JobInfo.TriggerContentUri(IMAGES_MEDIA_URI, 0))
@@ -907,6 +1000,13 @@
assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
}
+ private void markExpeditedQuotaApproved(JobStatus job, boolean isApproved) {
+ if (job.isRequestedExpeditedJob()) {
+ job.setExpeditedJobQuotaApproved(sElapsedRealtimeClock.millis(), isApproved);
+ job.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), isApproved);
+ }
+ }
+
private void markImplicitConstraintsSatisfied(JobStatus job, boolean isSatisfied) {
job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
job.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index 450cc40..908e717 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'
+ credentialShareableWithParent='false'
/>
</profile-type>
<profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index 653ed1a..0b25f38 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -17,39 +17,85 @@
package com.android.server;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.job.JobScheduler;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class BinaryTransparencyServiceTest {
+ private static final String TAG = "BinaryTransparencyServiceTest";
+
private Context mContext;
private BinaryTransparencyService mBinaryTransparencyService;
private BinaryTransparencyService.BinaryTransparencyServiceImpl mTestInterface;
+ private DeviceConfig.Properties mOriginalBiometricsFlags;
+
+ @Mock
+ private BinaryTransparencyService.BiometricLogger mBiometricLogger;
+ @Mock
+ private FingerprintManager mFpManager;
+ @Mock
+ private FaceManager mFaceManager;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
mContext = spy(ApplicationProvider.getApplicationContext());
- mBinaryTransparencyService = new BinaryTransparencyService(mContext);
+ mBinaryTransparencyService = new BinaryTransparencyService(mContext, mBiometricLogger);
mTestInterface = mBinaryTransparencyService.new BinaryTransparencyServiceImpl();
+ mOriginalBiometricsFlags = DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BIOMETRICS);
+
+ when(mContext.getSystemService(FingerprintManager.class)).thenReturn(mFpManager);
+ when(mContext.getSystemService(FaceManager.class)).thenReturn(mFaceManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ try {
+ DeviceConfig.setProperties(mOriginalBiometricsFlags);
+ } catch (DeviceConfig.BadConfigException e) {
+ Log.e(TAG, "Failed to reset biometrics flags to the original values before test. "
+ + e);
+ }
}
private void prepSignedInfo() {
@@ -120,4 +166,105 @@
}
}
+ @Test
+ public void testCollectBiometricProperties_disablesFeature() {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS,
+ BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION,
+ Boolean.FALSE.toString(),
+ false /* makeDefault */);
+
+ mBinaryTransparencyService.collectBiometricProperties();
+
+ verify(mFpManager, never()).getSensorPropertiesInternal();
+ verify(mFaceManager, never()).getSensorProperties();
+ }
+
+ @Test
+ public void testCollectBiometricProperties_enablesFeature() {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS,
+ BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION,
+ Boolean.TRUE.toString(),
+ false /* makeDefault */);
+
+ mBinaryTransparencyService.collectBiometricProperties();
+
+ verify(mFpManager, times(1)).getSensorPropertiesInternal();
+ verify(mFaceManager, times(1)).getSensorProperties();
+ }
+
+ @Test
+ public void testCollectBiometricProperties_enablesFeature_logsFingerprintProperties() {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS,
+ BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION,
+ Boolean.TRUE.toString(),
+ false /* makeDefault */);
+ final List<FingerprintSensorPropertiesInternal> props = List.of(
+ new FingerprintSensorPropertiesInternal(
+ 1 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ List.of(new ComponentInfoInternal("sensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */, "00000001" /* serialNumber */,
+ "" /* softwareVersion */)),
+ FingerprintSensorProperties.TYPE_REAR,
+ true /* resetLockoutRequiresHardwareAuthToken */));
+ when(mFpManager.getSensorPropertiesInternal()).thenReturn(props);
+
+ mBinaryTransparencyService.collectBiometricProperties();
+
+ verify(mBiometricLogger, times(1)).logStats(
+ eq(1) /* sensorId */,
+ eq(FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__MODALITY__MODALITY_FINGERPRINT),
+ eq(FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_REAR),
+ eq(FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_STRONG),
+ eq("sensor") /* componentId */,
+ eq("vendor/model/revision") /* hardwareVersion */,
+ eq("1.01") /* firmwareVersion */,
+ eq("00000001") /* serialNumber */,
+ eq("") /* softwareVersion */
+ );
+ }
+
+ @Test
+ public void testCollectBiometricProperties_enablesFeature_logsFaceProperties() {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS,
+ BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION,
+ Boolean.TRUE.toString(),
+ false /* makeDefault */);
+ final List<FaceSensorProperties> props = List.of(FaceSensorProperties.from(
+ new FaceSensorPropertiesInternal(
+ 1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 1 /* maxEnrollmentsPerUser */,
+ List.of(new ComponentInfoInternal("sensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */, "00000001" /* serialNumber */,
+ "" /* softwareVersion */)),
+ FaceSensorProperties.TYPE_RGB,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */)));
+ when(mFaceManager.getSensorProperties()).thenReturn(props);
+
+ mBinaryTransparencyService.collectBiometricProperties();
+
+ verify(mBiometricLogger, times(1)).logStats(
+ eq(1) /* sensorId */,
+ eq(FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__MODALITY__MODALITY_FACE),
+ eq(FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FACE_RGB),
+ eq(FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_CONVENIENCE),
+ eq("sensor") /* componentId */,
+ eq("vendor/model/revision") /* hardwareVersion */,
+ eq("1.01") /* firmwareVersion */,
+ eq("00000001") /* serialNumber */,
+ eq("") /* softwareVersion */
+ );
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index aec70f4..6216c66 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,16 +16,23 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.MANAGE_BIOMETRIC;
+import static android.Manifest.permission.TEST_BIOMETRIC;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,11 +60,14 @@
import com.android.internal.R;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
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;
+import org.mockito.stubbing.Stubber;
import java.util.List;
@@ -65,11 +75,13 @@
@SmallTest
public class AuthServiceTest {
- private static final String TAG = "AuthServiceTest";
private static final String TEST_OP_PACKAGE_NAME = "test_package";
private AuthService mAuthService;
+ @Rule
+ public MockitoRule mockitorule = MockitoJUnit.rule();
+
@Mock
private Context mContext;
@Mock
@@ -97,8 +109,6 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
// Placeholder test config
final String[] config = {
"0:2:15", // ID0:Fingerprint:Strong
@@ -123,10 +133,13 @@
when(mInjector.getIrisService()).thenReturn(mIrisService);
when(mInjector.getAppOps(any())).thenReturn(mAppOpsManager);
when(mInjector.isHidlDisabled(any())).thenReturn(false);
+
+ setInternalAndTestBiometricPermissions(mContext, false /* hasPermission */);
}
@Test
public void testRegisterNullService_doesNotRegister() throws Exception {
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
// Config contains Fingerprint, Iris, Face, but services are all null
@@ -260,6 +273,40 @@
}
@Test
+ public void testAuthenticate_throwsWhenUsingTestConfigurations() {
+ final PromptInfo promptInfo = mock(PromptInfo.class);
+ when(promptInfo.containsPrivateApiConfigurations()).thenReturn(false);
+ when(promptInfo.containsTestConfigurations()).thenReturn(true);
+
+ testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo);
+ }
+
+ @Test
+ public void testAuthenticate_throwsWhenUsingPrivateApis() {
+ final PromptInfo promptInfo = mock(PromptInfo.class);
+ when(promptInfo.containsPrivateApiConfigurations()).thenReturn(true);
+ when(promptInfo.containsTestConfigurations()).thenReturn(false);
+
+ testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo);
+ }
+
+ private void testAuthenticate_throwsWhenUsingTestConfigurations(PromptInfo promptInfo) {
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ assertThrows(SecurityException.class, () -> {
+ mAuthService.mImpl.authenticate(
+ null /* token */,
+ 10 /* sessionId */,
+ 2 /* userId */,
+ mReceiver,
+ TEST_OP_PACKAGE_NAME,
+ promptInfo);
+ waitForIdle();
+ });
+ }
+
+ @Test
public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws Exception {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
@@ -283,8 +330,10 @@
}
@Test
- public void testHasEnrolledBiometrics_callsBiometricServiceHasEnrolledBiometrics() throws
- Exception {
+ public void testHasEnrolledBiometrics_callsBiometricServiceHasEnrolledBiometrics()
+ throws Exception {
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
@@ -307,6 +356,8 @@
@Test
public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback()
throws Exception {
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
@@ -320,6 +371,20 @@
eq(callback), eq(UserHandle.getCallingUserId()));
}
+ private static void setInternalAndTestBiometricPermissions(
+ Context context, boolean hasPermission) {
+ for (String p : List.of(TEST_BIOMETRIC, MANAGE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) {
+ when(context.checkCallingPermission(eq(p))).thenReturn(hasPermission
+ ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
+ when(context.checkCallingOrSelfPermission(eq(p))).thenReturn(hasPermission
+ ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
+ final Stubber doPermCheck =
+ hasPermission ? doNothing() : doThrow(SecurityException.class);
+ doPermCheck.when(context).enforceCallingPermission(eq(p), any());
+ doPermCheck.when(context).enforceCallingOrSelfPermission(eq(p), any());
+ }
+ }
+
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
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
index d71e1ba..c7fb97f 100644
--- 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
@@ -35,6 +35,9 @@
@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(
@@ -171,10 +174,123 @@
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("name");
+ parcelableCallBuilder.setCallerDisplayName(CALLER_DISPLAY_NAME);
+ parcelableCallBuilder.setContactDisplayName(CONTACT_DISPLAY_NAME);
parcelableCallBuilder.setCapabilities(capabilities);
parcelableCallBuilder.setState(state);
parcelableCallBuilder.setConferenceableCallIds(Collections.emptyList());
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 6a4435f..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
@@ -634,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);
@@ -645,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(
@@ -1059,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,
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 4998a6c..60483f1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -134,6 +134,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.security.KeyChain;
import android.security.keystore.AttestationUtils;
@@ -259,6 +260,8 @@
private static final String PROFILE_OFF_SUSPENSION_TITLE = "suspension_title";
private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text";
private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text";
+ private static final String FLAG_ENABLE_WORK_PROFILE_TELEPHONY =
+ "enable_work_profile_telephony";
@Before
public void setUp() throws Exception {
@@ -4982,7 +4985,8 @@
public void testWipeDataManagedProfileOnOrganizationOwnedDevice() throws Exception {
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
-
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "true", false);
// Even if the caller is the managed profile, the current user is the user 0
when(getServices().iactivityManager.getCurrentUser())
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
@@ -5043,6 +5047,8 @@
verify(getServices().packageManagerInternal)
.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
new file mode 100644
index 0000000..24fc348
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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 a1e5ce7..2655c3f 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -96,6 +96,7 @@
private Binder mDisplayToken;
private String mDisplayUniqueId;
private Context mContextSpy;
+ private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@@ -118,6 +119,7 @@
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);
@@ -134,7 +136,8 @@
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
- mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy);
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
+ null, mContextSpy);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@@ -144,7 +147,8 @@
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
- mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy);
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, 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);
@@ -699,9 +703,12 @@
// 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, () -> {}, mContextSpy);
+ DEFAULT_HBM_DATA, null, () -> {}, mHighBrightnessModeMetadata, 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
new file mode 100644
index 0000000..ede54e0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
index 93b151e..9f295b8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
@@ -175,7 +175,11 @@
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 4d8d25a..9c1b670 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -137,11 +137,17 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_1,
- true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_1)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
hdmiPortInfos[1] =
- new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_2,
- true, false, false);
+ new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_2)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index bb50a89..93508d0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -112,7 +112,11 @@
public HdmiPortInfo[] nativeGetPortInfos() {
if (mHdmiPortInfo == null) {
mHdmiPortInfo = new HdmiPortInfo[1];
- mHdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true);
+ mHdmiPortInfo[0] = new HdmiPortInfo.Builder(1, 1, 0x1000)
+ .setCecSupported(true)
+ .setMhlSupported(true)
+ .setArcSupported(true)
+ .build();
}
return mHdmiPortInfo;
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index f27587e..e3d9558 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -118,7 +118,11 @@
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 90acc99..f5c0f2a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -197,17 +197,29 @@
mHdmiCecLocalDeviceAudioSystem.setRoutingControlFeatureEnabled(true);
mHdmiPortInfo = new HdmiPortInfo[4];
mHdmiPortInfo[0] =
- new HdmiPortInfo(
- 0, HdmiPortInfo.PORT_INPUT, SELF_PHYSICAL_ADDRESS, true, false, false);
+ new HdmiPortInfo.Builder(0, HdmiPortInfo.PORT_INPUT, SELF_PHYSICAL_ADDRESS)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mHdmiPortInfo[1] =
- new HdmiPortInfo(
- 2, HdmiPortInfo.PORT_INPUT, HDMI_1_PHYSICAL_ADDRESS, true, false, false);
+ new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, HDMI_1_PHYSICAL_ADDRESS)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mHdmiPortInfo[2] =
- new HdmiPortInfo(
- 1, HdmiPortInfo.PORT_INPUT, HDMI_2_PHYSICAL_ADDRESS, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, HDMI_2_PHYSICAL_ADDRESS)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mHdmiPortInfo[3] =
- new HdmiPortInfo(
- 4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS, true, false, false);
+ new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(mHdmiPortInfo);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index dfab207..beba9c6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -150,7 +150,11 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 3796ce9..9c5c0d4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -189,7 +189,11 @@
mLocalDevices.add(mHdmiLocalDevice);
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index cb1e78b..ccfc2b9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -193,10 +193,19 @@
mHdmiControlService.setEarcController(mHdmiEarcController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
- hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false, false);
+ hdmiPortInfos[0] = new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x1000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .setEarcSupported(false)
+ .build();
hdmiPortInfos[1] =
- new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true, true);
+ new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, 0x2000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(true)
+ .setEarcSupported(true)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index a82a79f..d341153 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -92,15 +92,35 @@
mHdmiPortInfo = new HdmiPortInfo[5];
mHdmiPortInfo[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x2100)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mHdmiPortInfo[1] =
- new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false);
+ new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, 0x2200)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mHdmiPortInfo[2] =
- new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+ new HdmiPortInfo.Builder(3, HdmiPortInfo.PORT_INPUT, 0x2000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mHdmiPortInfo[3] =
- new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
+ new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_INPUT, 0x3000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mHdmiPortInfo[4] =
- new HdmiPortInfo(5, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ new HdmiPortInfo.Builder(5, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(mHdmiPortInfo);
mHdmiCecNetwork.initPortInfo();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 8e5bb13..55e8b20 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -99,7 +99,11 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index cd6dfbf..8baad61 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -139,13 +139,33 @@
mLocalDevices.add(mPlaybackDeviceSpy);
mHdmiPortInfo = new HdmiPortInfo[4];
mHdmiPortInfo[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x2100)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .setEarcSupported(false)
+ .build();
mHdmiPortInfo[1] =
- new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false, false);
+ new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, 0x2200)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .setEarcSupported(false)
+ .build();
mHdmiPortInfo[2] =
- new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true, true);
+ new HdmiPortInfo.Builder(3, HdmiPortInfo.PORT_INPUT, 0x2000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(true)
+ .setEarcSupported(true)
+ .build();
mHdmiPortInfo[3] =
- new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false, false);
+ new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_INPUT, 0x3000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .setEarcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(mHdmiPortInfo);
mHdmiControlServiceSpy.initService();
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index a623841..89743cd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -102,9 +102,17 @@
mTestLooper.dispatchAll();
HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[2];
hdmiPortInfo[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x1000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
hdmiPortInfo[1] =
- new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+ new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, 0x2000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfo);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 4e8cf4a..5b1bdf6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -180,8 +180,11 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_AVR,
- true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_AVR)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 70f9e5c..c40cd0e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -102,9 +102,17 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x1000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .build();
hdmiPortInfos[1] =
- new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
+ new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, 0x2000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(true)
+ .build();
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index f2e03aa..e871fc5 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -37,7 +37,6 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -165,18 +164,6 @@
awaitJobStart(DEFAULT_WAIT_TIMEOUT));
}
- @FlakyTest
- @Test
- public void testFeatureFlag() throws Exception {
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.FORCED_APP_STANDBY_ENABLED, 0);
- scheduleAndAssertJobStarted();
- setAppOpsModeAllowed(false);
- mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
- assertFalse("Job stopped even when feature flag was disabled",
- awaitJobStop(DEFAULT_WAIT_TIMEOUT, JobParameters.STOP_REASON_UNDEFINED));
- }
-
@After
public void tearDown() throws Exception {
final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
@@ -187,8 +174,6 @@
Thread.sleep(500); // To avoid race with register in the next setUp
setAppOpsModeAllowed(true);
setPowerExemption(false);
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.FORCED_APP_STANDBY_ENABLED, 1);
}
private void setPowerExemption(boolean exempt) throws RemoteException {
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..ade1bd4 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)
+ .setMediaSharedWithParent(false)
+ .setCredentialShareableWithParent(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.setMediaSharedWithParent(true);
+ actualProps.setCredentialShareableWithParent(false);
// Write the properties to xml.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -111,6 +115,7 @@
.setStartWithParent(true)
.setShowInSettings(3452)
.setInheritDevicePolicy(1732)
+ .setMediaSharedWithParent(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::isMediaSharedWithParent,
+ copy::isMediaSharedWithParent, true);
+ assertEqualGetterOrThrows(orig::isCredentialShareableWithParent,
+ copy::isCredentialShareableWithParent, true);
}
/**
@@ -215,5 +223,9 @@
.isEqualTo(actual.getCrossProfileIntentFilterAccessControl());
assertThat(expected.getCrossProfileIntentResolutionStrategy())
.isEqualTo(actual.getCrossProfileIntentResolutionStrategy());
+ assertThat(expected.isMediaSharedWithParent())
+ .isEqualTo(actual.isMediaSharedWithParent());
+ assertThat(expected.isCredentialShareableWithParent())
+ .isEqualTo(actual.isCredentialShareableWithParent());
}
}
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..702059d 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)
+ .setMediaSharedWithParent(true)
+ .setCredentialShareableWithParent(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().isMediaSharedWithParent());
+ assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
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.isMediaSharedWithParent());
+ assertFalse(props.isCredentialShareableWithParent());
assertFalse(type.hasBadge());
}
@@ -279,7 +285,9 @@
.setStartWithParent(true)
.setUseParentsContacts(true)
.setCrossProfileIntentFilterAccessControl(10)
- .setCrossProfileIntentResolutionStrategy(1);
+ .setCrossProfileIntentResolutionStrategy(1)
+ .setMediaSharedWithParent(false)
+ .setCredentialShareableWithParent(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().isMediaSharedWithParent());
+ assertTrue(aospType.getDefaultUserPropertiesReference()
+ .isCredentialShareableWithParent());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -348,6 +359,9 @@
assertFalse(aospType.getDefaultUserPropertiesReference().getStartWithParent());
assertFalse(aospType.getDefaultUserPropertiesReference()
.getUseParentsContacts());
+ assertTrue(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent());
+ assertFalse(aospType.getDefaultUserPropertiesReference()
+ .isCredentialShareableWithParent());
// 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..ac5bcff 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.isMediaSharedWithParent())
+ .isEqualTo(cloneUserProperties.isMediaSharedWithParent());
+ assertThat(typeProps.isCredentialShareableWithParent())
+ .isEqualTo(cloneUserProperties.isCredentialShareableWithParent());
// 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.isMediaSharedWithParent()).isFalse();
+ assertThat(userProps.isCredentialShareableWithParent()).isTrue();
}
// Make sure only max managed profiles can be created
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 3b7b5eb..9ff8756 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2603,6 +2603,133 @@
}
@Test
+ public void testIsHorizontalReachabilityEnabled_splitScreen_false() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ setUpDisplaySizeWithApp(2800, 1000);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Unresizable portrait-only activity.
+ prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT);
+
+ // 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, 1400, 1000);
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+ // Horizontal reachability is disabled because the app is in split screen.
+ assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ }
+
+ @Test
+ public void testIsVerticalReachabilityEnabled_splitScreen_false() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ setUpDisplaySizeWithApp(1000, 2800);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Unresizable landscape-only activity.
+ prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE);
+
+ // 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, 1000, 1400);
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+ // Vertical reachability is disabled because the app is in split screen.
+ assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ }
+
+ @Test
+ public void testIsVerticalReachabilityEnabled_doesNotMatchParentWidth_false() {
+ setUpDisplaySizeWithApp(1000, 2800);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+
+ // Unresizable landscape-only activity.
+ prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE);
+
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ // Activity now in size compat mode.
+ assertTrue(mActivity.inSizeCompatMode());
+
+ // Vertical reachability is disabled because the app does not match parent width
+ assertNotEquals(mActivity.getBounds().width(), mActivity.mDisplayContent.getBounds()
+ .width());
+ assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ }
+
+ @Test
+ public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() {
+ setUpDisplaySizeWithApp(2800, 1000);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+
+ // Unresizable portrait-only activity.
+ prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ // Activity now in size compat mode.
+ assertTrue(mActivity.inSizeCompatMode());
+
+ // Horizontal reachability is disabled because the app does not match parent height
+ assertNotEquals(mActivity.getBounds().height(), mActivity.mDisplayContent.getBounds()
+ .height());
+ assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ }
+
+ @Test
+ public void testIsHorizontalReachabilityEnabled_inSizeCompatMode_matchesParentHeight_true() {
+ setUpDisplaySizeWithApp(1800, 2200);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+
+ // Unresizable portrait-only activity.
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ // Activity now in size compat mode.
+ assertTrue(mActivity.inSizeCompatMode());
+
+ // Horizontal reachability is enabled because the app matches parent height
+ assertEquals(mActivity.getBounds().height(), mActivity.mDisplayContent.getBounds()
+ .height());
+ assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ }
+
+ @Test
+ public void testIsVerticalReachabilityEnabled_inSizeCompatMode_matchesParentWidth_true() {
+ setUpDisplaySizeWithApp(2200, 1800);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+
+ // Unresizable landscape-only activity.
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ // Activity now in size compat mode.
+ assertTrue(mActivity.inSizeCompatMode());
+
+ // Vertical reachability is enabled because the app matches parent width
+ assertEquals(mActivity.getBounds().width(), mActivity.mDisplayContent.getBounds().width());
+ assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ }
+
+ @Test
public void testLetterboxDetailsForStatusBar_noLetterbox() {
setUpDisplaySizeWithApp(2800, 1000);
addStatusBar(mActivity.mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index d31ae6a..83be4f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.Surface.ROTATION_0;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -78,6 +79,12 @@
final InputMonitor inputMonitor = getInputMonitor();
spyOn(inputMonitor);
doNothing().when(inputMonitor).resumeDispatchingLw(any());
+
+ // For devices that set the sysprop ro.bootanim.set_orientation_<display_id>
+ // See DisplayRotation#readDefaultDisplayRotation for context.
+ // Without that, meaning of height and width in context of the tests can be swapped if
+ // the default rotation is 90 or 270.
+ displayRotation.setRotation(ROTATION_0);
}
public static class Builder {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsIdleService.java b/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
index 3163820..20f03d8 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
@@ -15,6 +15,7 @@
*/
package com.android.server.usage;
+import android.annotation.UserIdInt;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -35,65 +36,71 @@
public class UsageStatsIdleService extends JobService {
/**
- * Base job ID for the pruning job - must be unique within the system server uid.
+ * Namespace for prune job
*/
- private static final int PRUNE_JOB_ID = 546357475;
+ private static final String PRUNE_JOB_NS = "usagestats_prune";
+
/**
- * Job ID for the update mappings job - must be unique within the system server uid.
- * Incrementing PRUNE_JOB_ID by 21475 (MAX_USER_ID) to ensure there is no overlap in job ids.
+ * Namespace for update mappings job
*/
- private static final int UPDATE_MAPPINGS_JOB_ID = 546378950;
+ private static final String UPDATE_MAPPINGS_JOB_NS = "usagestats_mapping";
private static final String USER_ID_KEY = "user_id";
- static void scheduleJob(Context context, int userId) {
- final int userJobId = PRUNE_JOB_ID + userId; // unique job id per user
+ /** Schedule a prune job */
+ static void schedulePruneJob(Context context, @UserIdInt int userId) {
final ComponentName component = new ComponentName(context.getPackageName(),
UsageStatsIdleService.class.getName());
final PersistableBundle bundle = new PersistableBundle();
bundle.putInt(USER_ID_KEY, userId);
- final JobInfo pruneJob = new JobInfo.Builder(userJobId, component)
+ final JobInfo pruneJob = new JobInfo.Builder(userId, component)
.setRequiresDeviceIdle(true)
.setExtras(bundle)
.setPersisted(true)
.build();
- scheduleJobInternal(context, pruneJob, userJobId);
+ scheduleJobInternal(context, pruneJob, PRUNE_JOB_NS, userId);
}
- static void scheduleUpdateMappingsJob(Context context) {
+ static void scheduleUpdateMappingsJob(Context context, @UserIdInt int userId) {
final ComponentName component = new ComponentName(context.getPackageName(),
UsageStatsIdleService.class.getName());
- final JobInfo updateMappingsJob = new JobInfo.Builder(UPDATE_MAPPINGS_JOB_ID, component)
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putInt(USER_ID_KEY, userId);
+ final JobInfo updateMappingsJob = new JobInfo.Builder(userId, component)
.setPersisted(true)
.setMinimumLatency(TimeUnit.DAYS.toMillis(1))
.setOverrideDeadline(TimeUnit.DAYS.toMillis(2))
+ .setExtras(bundle)
.build();
- scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_ID);
+ scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_NS, userId);
}
- private static void scheduleJobInternal(Context context, JobInfo pruneJob, int jobId) {
- final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
- final JobInfo pendingPruneJob = jobScheduler.getPendingJob(jobId);
- // only schedule a new prune job if one doesn't exist already for this user
- if (!pruneJob.equals(pendingPruneJob)) {
- jobScheduler.cancel(jobId); // cancel any previously scheduled prune job
- jobScheduler.schedule(pruneJob);
+ private static void scheduleJobInternal(Context context, JobInfo jobInfo,
+ String namespace, int jobId) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ jobScheduler = jobScheduler.forNamespace(namespace);
+ final JobInfo pendingJob = jobScheduler.getPendingJob(jobId);
+ // only schedule a new job if one doesn't exist already for this user
+ if (!jobInfo.equals(pendingJob)) {
+ jobScheduler.cancel(jobId); // cancel any previously scheduled job
+ jobScheduler.schedule(jobInfo);
}
}
- static void cancelJob(Context context, int userId) {
- cancelJobInternal(context, PRUNE_JOB_ID + userId);
+ static void cancelPruneJob(Context context, @UserIdInt int userId) {
+ cancelJobInternal(context, PRUNE_JOB_NS, userId);
}
- static void cancelUpdateMappingsJob(Context context) {
- cancelJobInternal(context, UPDATE_MAPPINGS_JOB_ID);
+ static void cancelUpdateMappingsJob(Context context, @UserIdInt int userId) {
+ cancelJobInternal(context, UPDATE_MAPPINGS_JOB_NS, userId);
}
- private static void cancelJobInternal(Context context, int jobId) {
- final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ private static void cancelJobInternal(Context context, String namespace, int jobId) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler != null) {
+ jobScheduler = jobScheduler.forNamespace(namespace);
jobScheduler.cancel(jobId);
}
}
@@ -102,15 +109,19 @@
public boolean onStartJob(JobParameters params) {
final PersistableBundle bundle = params.getExtras();
final int userId = bundle.getInt(USER_ID_KEY, -1);
- if (userId == -1 && params.getJobId() != UPDATE_MAPPINGS_JOB_ID) {
+
+ if (userId == -1) { // legacy job
return false;
}
+ // Do async
AsyncTask.execute(() -> {
final UsageStatsManagerInternal usageStatsManagerInternal = LocalServices.getService(
UsageStatsManagerInternal.class);
- if (params.getJobId() == UPDATE_MAPPINGS_JOB_ID) {
- final boolean jobFinished = usageStatsManagerInternal.updatePackageMappingsData();
+ final String jobNs = params.getJobNamespace();
+ if (UPDATE_MAPPINGS_JOB_NS.equals(jobNs)) {
+ final boolean jobFinished =
+ usageStatsManagerInternal.updatePackageMappingsData(userId);
jobFinished(params, !jobFinished); // reschedule if data was not updated
} else {
final boolean jobFinished =
@@ -118,6 +129,8 @@
jobFinished(params, !jobFinished); // reschedule if data was not pruned
}
});
+
+ // Job is running asynchronously
return true;
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index b3a1f2b..7ff5b4a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -433,11 +433,9 @@
private void onUserUnlocked(int userId) {
// fetch the installed packages outside the lock so it doesn't block package manager.
final HashMap<String, Long> installedPackages = getInstalledPackages(userId);
- // delay updating of package mappings for user 0 since their data is not likely to be stale.
- // this also makes it less likely for restored data to be erased on unexpected reboots.
- if (userId == UserHandle.USER_SYSTEM) {
- UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
- }
+
+ UsageStatsIdleService.scheduleUpdateMappingsJob(getContext(), userId);
+
final boolean deleteObsoleteData = shouldDeleteObsoleteData(UserHandle.of(userId));
synchronized (mLock) {
// This should be safe to add this early. Other than reportEventOrAddToQueue and
@@ -1261,8 +1259,8 @@
}
mAppStandby.onUserRemoved(userId);
// Cancel any scheduled jobs for this user since the user is being removed.
- UsageStatsIdleService.cancelJob(getContext(), userId);
- UsageStatsIdleService.cancelUpdateMappingsJob(getContext());
+ UsageStatsIdleService.cancelPruneJob(getContext(), userId);
+ UsageStatsIdleService.cancelUpdateMappingsJob(getContext(), userId);
}
/**
@@ -1300,7 +1298,7 @@
// Schedule a job to prune any data related to this package.
if (tokenRemoved != PackagesTokenData.UNASSIGNED_TOKEN) {
- UsageStatsIdleService.scheduleJob(getContext(), userId);
+ UsageStatsIdleService.schedulePruneJob(getContext(), userId);
}
}
@@ -1325,19 +1323,19 @@
/**
* Called by the Binder stub.
*/
- private boolean updatePackageMappingsData() {
+ private boolean updatePackageMappingsData(@UserIdInt int userId) {
// don't update the mappings if a profile user is defined
- if (!shouldDeleteObsoleteData(UserHandle.SYSTEM)) {
+ if (!shouldDeleteObsoleteData(UserHandle.of(userId))) {
return true; // return true so job scheduler doesn't reschedule the job
}
// fetch the installed packages outside the lock so it doesn't block package manager.
- final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
+ final HashMap<String, Long> installedPkgs = getInstalledPackages(userId);
synchronized (mLock) {
- if (!mUserUnlockedStates.contains(UserHandle.USER_SYSTEM)) {
+ if (!mUserUnlockedStates.contains(userId)) {
return false; // user is no longer unlocked
}
- final UserUsageStatsService userService = mUserState.get(UserHandle.USER_SYSTEM);
+ final UserUsageStatsService userService = mUserState.get(userId);
if (userService == null) {
return false; // user was stopped or removed
}
@@ -3055,44 +3053,35 @@
}
@Override
- public byte[] getBackupPayload(int user, String key) {
- if (!mUserUnlockedStates.contains(user)) {
- Slog.w(TAG, "Failed to get backup payload for locked user " + user);
+ public byte[] getBackupPayload(@UserIdInt int userId, String key) {
+ if (!mUserUnlockedStates.contains(userId)) {
+ Slog.w(TAG, "Failed to get backup payload for locked user " + userId);
return null;
}
synchronized (mLock) {
- // Check to ensure that only user 0's data is b/r for now
- // Note: if backup and restore is enabled for users other than the system user, the
- // #onUserUnlocked logic, specifically when the update mappings job is scheduled via
- // UsageStatsIdleService.scheduleUpdateMappingsJob, will have to be updated.
- if (user == UserHandle.USER_SYSTEM) {
- final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(user);
- if (userStats == null) {
- return null; // user was stopped or removed
- }
- return userStats.getBackupPayload(key);
- } else {
- return null;
+ final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(userId);
+ if (userStats == null) {
+ return null; // user was stopped or removed
}
+ Slog.i(TAG, "Returning backup payload for u=" + userId);
+ return userStats.getBackupPayload(key);
}
}
@Override
- public void applyRestoredPayload(int user, String key, byte[] payload) {
+ public void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload) {
synchronized (mLock) {
- if (!mUserUnlockedStates.contains(user)) {
- Slog.w(TAG, "Failed to apply restored payload for locked user " + user);
+ if (!mUserUnlockedStates.contains(userId)) {
+ Slog.w(TAG, "Failed to apply restored payload for locked user " + userId);
return;
}
- if (user == UserHandle.USER_SYSTEM) {
- final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(user);
- if (userStats == null) {
- return; // user was stopped or removed
- }
- final Set<String> restoredApps = userStats.applyRestoredPayload(key, payload);
- mAppStandby.restoreAppsToRare(restoredApps, user);
+ final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(userId);
+ if (userStats == null) {
+ return; // user was stopped or removed
}
+ final Set<String> restoredApps = userStats.applyRestoredPayload(key, payload);
+ mAppStandby.restoreAppsToRare(restoredApps, userId);
}
}
@@ -3165,8 +3154,8 @@
}
@Override
- public boolean updatePackageMappingsData() {
- return UsageStatsService.this.updatePackageMappingsData();
+ public boolean updatePackageMappingsData(@UserIdInt int userId) {
+ return UsageStatsService.this.updatePackageMappingsData(userId);
}
/**
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 28d726e..1a1af3b 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -866,7 +866,8 @@
public void simulateDisplayPortAltModeInfo(String portId, int partnerSinkStatus,
- int cableStatus, int numLanes, IndentingPrintWriter pw) {
+ int cableStatus, int numLanes, boolean hpd, int linkTrainingStatus,
+ IndentingPrintWriter pw) {
synchronized (mLock) {
final RawPortInfo portInfo = mSimulatedPorts.get(portId);
if (portInfo == null) {
@@ -875,7 +876,8 @@
}
DisplayPortAltModeInfo displayPortAltModeInfo =
- new DisplayPortAltModeInfo(partnerSinkStatus, cableStatus, numLanes);
+ new DisplayPortAltModeInfo(partnerSinkStatus, cableStatus, numLanes, hpd,
+ linkTrainingStatus);
portInfo.displayPortAltModeInfo = displayPortAltModeInfo;
pw.println("Simulating DisplayPort Info: " + displayPortAltModeInfo);
updatePortsLocked(pw, null);
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 7d84222..0aa1715 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -1169,14 +1169,17 @@
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
}
- } else if ("set-displayport-status".equals(args[0]) && args.length == 5) {
+ } else if ("set-displayport-status".equals(args[0]) && args.length == 7) {
final String portId = args[1];
final int partnerSinkStatus = Integer.parseInt(args[2]);
final int cableStatus = Integer.parseInt(args[3]);
final int displayPortNumLanes = Integer.parseInt(args[4]);
+ final boolean hpd = Boolean.parseBoolean(args[5]);
+ final int linkTrainingStatus = Integer.parseInt(args[6]);
if (mPortManager != null) {
mPortManager.simulateDisplayPortAltModeInfo(portId,
- partnerSinkStatus, cableStatus, displayPortNumLanes, pw);
+ partnerSinkStatus, cableStatus, displayPortNumLanes,
+ hpd, linkTrainingStatus, pw);
pw.println();
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
@@ -1187,7 +1190,10 @@
mPortManager.simulateDisplayPortAltModeInfo(portId,
DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN,
DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN,
- 0, pw);
+ 0,
+ false,
+ 0,
+ pw);
pw.println();
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
@@ -1259,7 +1265,13 @@
pw.println("Example simulate DisplayPort Alt Mode Changes:");
pw.println(" dumpsys usb add-port \"matrix\" dual --displayport");
pw.println(" dumpsys usb set-displayport-status \"matrix\" <partner-sink>"
- + " <cable> <num-lanes>");
+ + " <cable> <num-lanes> <hpd> <link-training-status>");
+ pw.println("The required fields are as followed:");
+ pw.println(" <partner-sink>: type DisplayPortAltModeStatus");
+ pw.println(" <cable>: type DisplayPortAltModeStatus");
+ pw.println(" <num-lanes>: type int, expected 0, 2, or 4");
+ pw.println(" <hpd>: type boolean, expected true or false");
+ pw.println(" <link-training-status>: type int with range [0,2]");
pw.println(" dumpsys usb reset-displayport-status \"matrix\"");
pw.println("reset-displayport-status can also be used in order to set");
pw.println("the DisplayPortInfo to default values.");
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index b9ccace..c7a7a9b 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -639,7 +639,9 @@
altModeData.getDisplayPortAltModeData();
return new DisplayPortAltModeInfo(displayPortData.partnerSinkStatus,
displayPortData.cableStatus,
- toDisplayPortAltModeNumLanesInt(displayPortData.pinAssignment));
+ toDisplayPortAltModeNumLanesInt(displayPortData.pinAssignment),
+ displayPortData.hpd,
+ displayPortData.linkTrainingStatus);
}
}
return null;
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS b/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
index e5d0370..01b2cb9 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
@@ -1,2 +1,2 @@
-ytai@google.com
+atneya@google.com
elaurent@google.com
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index fd2907c..95a8e16 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -32,10 +32,8 @@
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.
@@ -150,14 +148,6 @@
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;
@@ -466,45 +456,9 @@
}
private void fireCallRemoved(Call 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;
+ for (Listener listener : mListeners) {
+ listener.onCallRemoved(this, call);
}
- return false;
}
private void fireCallAudioStateChanged(CallAudioState audioState) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 37d1e94..83b9098 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3242,17 +3242,17 @@
case NETWORK_TYPE_CDMA:
return "CDMA";
case NETWORK_TYPE_EVDO_0:
- return "EVDO-0";
+ return "CDMA - EvDo rev. 0";
case NETWORK_TYPE_EVDO_A:
- return "EVDO-A";
+ return "CDMA - EvDo rev. A";
case NETWORK_TYPE_EVDO_B:
- return "EVDO-B";
+ return "CDMA - EvDo rev. B";
case NETWORK_TYPE_1xRTT:
- return "1xRTT";
+ return "CDMA - 1xRTT";
case NETWORK_TYPE_LTE:
return "LTE";
case NETWORK_TYPE_EHRPD:
- return "eHRPD";
+ return "CDMA - eHRPD";
case NETWORK_TYPE_IDEN:
return "iDEN";
case NETWORK_TYPE_HSPAP:
@@ -3260,7 +3260,7 @@
case NETWORK_TYPE_GSM:
return "GSM";
case NETWORK_TYPE_TD_SCDMA:
- return "TD-SCDMA";
+ return "TD_SCDMA";
case NETWORK_TYPE_IWLAN:
return "IWLAN";
case NETWORK_TYPE_LTE_CA:
@@ -9574,10 +9574,10 @@
*/
public static boolean isValidAllowedNetworkTypesReason(@AllowedNetworkTypesReason int reason) {
switch (reason) {
- case ALLOWED_NETWORK_TYPES_REASON_USER:
- case ALLOWED_NETWORK_TYPES_REASON_POWER:
- case ALLOWED_NETWORK_TYPES_REASON_CARRIER:
- case ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER:
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS:
return true;
}
@@ -18118,44 +18118,4 @@
return "UNKNOWN(" + state + ")";
}
}
-
- /**
- * Convert the allowed network types reason to string.
- *
- * @param reason The allowed network types reason.
- * @return The converted string.
- *
- * @hide
- */
- @NonNull
- public static String allowedNetworkTypesReasonToString(@AllowedNetworkTypesReason int reason) {
- switch (reason) {
- case ALLOWED_NETWORK_TYPES_REASON_USER: return "user";
- case ALLOWED_NETWORK_TYPES_REASON_POWER: return "power";
- case ALLOWED_NETWORK_TYPES_REASON_CARRIER: return "carrier";
- case ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G: return "enable_2g";
- case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS: return "user_restrictions";
- default: return "unknown(" + reason + ")";
- }
- }
-
- /**
- * Convert the allowed network types reason from string.
- *
- * @param reason The reason in string format.
- * @return The allowed network types reason.
- *
- * @hide
- */
- @AllowedNetworkTypesReason
- public static int allowedNetworkTypesReasonFromString(@NonNull String reason) {
- switch (reason) {
- case "user": return ALLOWED_NETWORK_TYPES_REASON_USER;
- case "power": return ALLOWED_NETWORK_TYPES_REASON_POWER;
- 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: return -1;
- }
- }
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index a2d2019..cdb7d7c 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -1570,8 +1570,8 @@
/**
* Returns whether the passing portIndex is available.
- * A port is available if it is active without enabled profile on it or
- * calling app has carrier privilege over the profile installed on the selected port.
+ * A port is available if it is active without an enabled profile on it or calling app can
+ * activate a new profile on the selected port without any user interaction.
* Always returns false if the cardId is a physical card.
*
* @param portIndex is an enumeration of the ports available on the UICC.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
index 41316d8..8d2af38 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
@@ -17,8 +17,8 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.RequiresDevice
import android.view.KeyEvent
+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
@@ -140,6 +140,12 @@
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsVisibleAtStartAndEnd() {
+ super.navBarWindowIsVisibleAtStartAndEnd()
+ }
+
companion object {
/**
* Creates the test configurations.
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(